@next2d/builder 0.1.7 → 0.1.10

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 (75) hide show
  1. package/dist/cli.d.ts +59 -0
  2. package/dist/cli.js +141 -0
  3. package/dist/constants.d.ts +53 -0
  4. package/dist/constants.js +64 -0
  5. package/dist/context.d.ts +29 -0
  6. package/dist/context.js +19 -0
  7. package/dist/electron.d.ts +10 -0
  8. package/dist/electron.js +142 -0
  9. package/dist/index.js +18 -754
  10. package/dist/native.d.ts +27 -0
  11. package/dist/native.js +123 -0
  12. package/dist/utils.d.ts +27 -0
  13. package/dist/utils.js +40 -0
  14. package/dist/web.d.ts +18 -0
  15. package/dist/web.js +70 -0
  16. package/dist/xbox.d.ts +62 -0
  17. package/dist/xbox.js +426 -0
  18. package/package.json +12 -9
  19. package/templates/xbox/CMakeLists.txt +116 -11
  20. package/templates/xbox/README.md +34 -8
  21. package/templates/xbox/cmake/FetchDawn.cmake +47 -6
  22. package/templates/xbox/cmake/FindV8.cmake +15 -0
  23. package/templates/xbox/js/bootstrap.js +151 -28
  24. package/templates/xbox/js/selftest.js +740 -90
  25. package/templates/xbox/src/AssetLoader.cpp +115 -12
  26. package/templates/xbox/src/AssetLoader.h +4 -0
  27. package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
  28. package/templates/xbox/src/EmbeddedAssets.h +60 -0
  29. package/templates/xbox/src/EventLoop.cpp +13 -1
  30. package/templates/xbox/src/EventLoop.h +12 -0
  31. package/templates/xbox/src/bindings/Audio.cpp +211 -31
  32. package/templates/xbox/src/bindings/Bindings.h +10 -0
  33. package/templates/xbox/src/bindings/Canvas.cpp +77 -2
  34. package/templates/xbox/src/bindings/Canvas2D.cpp +496 -43
  35. package/templates/xbox/src/bindings/Console.cpp +6 -1
  36. package/templates/xbox/src/bindings/DomShims.cpp +143 -0
  37. package/templates/xbox/src/bindings/EventTarget.cpp +11 -0
  38. package/templates/xbox/src/bindings/Fetch.cpp +2 -8
  39. package/templates/xbox/src/bindings/Image.cpp +243 -15
  40. package/templates/xbox/src/bindings/Polyfills.cpp +1226 -0
  41. package/templates/xbox/src/bindings/RasterCore.h +105 -1
  42. package/templates/xbox/src/bindings/Video.cpp +153 -10
  43. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +844 -18
  44. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +7 -9
  45. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
  46. package/templates/xbox/src/gpu/DawnContext.cpp +245 -7
  47. package/templates/xbox/src/gpu/DawnContext.h +18 -0
  48. package/templates/xbox/src/main.cpp +459 -8
  49. package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
  50. package/templates/xbox/src/platform/AudioDecoder.h +18 -0
  51. package/templates/xbox/src/platform/AudioEngine.cpp +37 -6
  52. package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
  53. package/templates/xbox/src/platform/DecodeQueue.h +27 -0
  54. package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
  55. package/templates/xbox/src/platform/ImageDecoder.h +18 -0
  56. package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
  57. package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
  58. package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
  59. package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
  60. package/templates/xbox/src/v8/CodeCache.h +223 -0
  61. package/templates/xbox/src/v8/V8Runtime.cpp +76 -11
  62. package/templates/xbox/src/v8/V8Util.h +79 -0
  63. package/templates/xbox/src/v8/WeakHandle.h +45 -0
  64. package/templates/xbox/src/worker/WorkerRuntime.cpp +432 -48
  65. package/templates/xbox/src/worker/WorkerRuntime.h +43 -13
  66. package/templates/xbox/tests/check_local.sh +7 -2
  67. package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
  68. package/templates/xbox/tests/raster_test.cpp +50 -0
  69. package/templates/xbox/tests/windows_platform_test.cpp +75 -3
  70. package/templates/xbox/third_party/LICENSES.md +14 -0
  71. package/templates/xbox/third_party/dr_mp3.h +5412 -0
  72. package/templates/xbox/third_party/dr_wav.h +9131 -0
  73. package/templates/xbox/third_party/stb_image.h +7988 -0
  74. package/templates/xbox/third_party/stb_truetype.h +5079 -0
  75. package/templates/xbox/third_party/stb_vorbis.c +5584 -0
@@ -11,11 +11,14 @@
11
11
  #include "RasterCore.h"
12
12
  #include "platform/TextRasterizer.h"
13
13
  #include "v8/V8Util.h"
14
+ #include "v8/WeakHandle.h"
14
15
 
15
16
  #include <Windows.h>
16
17
 
17
18
  #include <algorithm>
19
+ #include <cmath>
18
20
  #include <cstdio>
21
+ #include <cstdlib>
19
22
  #include <cstring>
20
23
  #include <memory>
21
24
  #include <string>
@@ -49,6 +52,40 @@ struct State {
49
52
  // クリップ矩形 (デバイス座標)。TextField はテキスト枠(矩形パス)を clip() で
50
53
  // 指定して文字のはみ出しを抑える。
51
54
  raster::ClipRect clip;
55
+
56
+ // --- CRC2D の完全な描画状態 (getter/setter で往復可能にする) ---
57
+ // ラスタライズへ実際に反映するもの(textAlign/textBaseline 等)と、
58
+ // 状態として保持するだけのもの(shadow/composite 等の近似)を含む。
59
+ std::string fill_style_str = "#000000";
60
+ std::string stroke_style_str = "#000000";
61
+ std::string line_cap = "butt";
62
+ std::string line_join = "miter";
63
+ double miter_limit = 10.0;
64
+ double line_dash_offset = 0.0;
65
+ std::vector<double> line_dash;
66
+ double shadow_blur = 0.0;
67
+ std::string shadow_color = "rgba(0, 0, 0, 0)";
68
+ double shadow_offset_x = 0.0;
69
+ double shadow_offset_y = 0.0;
70
+ std::string text_align = "start";
71
+ std::string text_baseline = "alphabetic";
72
+ std::string direction = "inherit";
73
+ std::string global_composite_operation = "source-over";
74
+ bool image_smoothing_enabled = true;
75
+ std::string image_smoothing_quality = "low";
76
+ std::string filter = "none";
77
+ std::string font_kerning = "auto";
78
+ std::string letter_spacing = "0px";
79
+ std::string word_spacing = "0px";
80
+ std::string text_rendering = "auto";
81
+ };
82
+
83
+ // createLinearGradient / createPattern 等が返す塗りオブジェクトの実体。
84
+ // fillStyle/strokeStyle へ代入された際に代表色 (RepresentativeColor) を取り出す。
85
+ struct PaintObject {
86
+ enum Kind { Gradient, Pattern } kind = Gradient;
87
+ std::vector<std::pair<double, RGBA>> stops; // gradient
88
+ RGBA sample{128,128,128,255}; // pattern の代表色
52
89
  };
53
90
 
54
91
  // 2D コンテキストの実体。JS オブジェクトの内部フィールドに保持する。
@@ -57,6 +94,8 @@ struct Canvas2D {
57
94
  std::vector<SubPath> path;
58
95
  State state;
59
96
  std::vector<State> stack;
97
+ // 現在のパス開始点 (arcTo/未サブパス時の moveTo 補完用)
98
+ double cur_x = 0.0, cur_y = 0.0;
60
99
 
61
100
  Canvas2D(int w, int h) : surface(w, h) {}
62
101
  };
@@ -85,6 +124,33 @@ void AddPoint(Canvas2D* c, double x, double y, bool new_sub)
85
124
  c->path.push_back(SubPath{});
86
125
  }
87
126
  c->path.back().pts.push_back(p);
127
+ c->cur_x = x; // arcTo 等が参照する現在点 (ユーザー座標)
128
+ c->cur_y = y;
129
+ }
130
+
131
+ // 値 (色文字列 or gradient/pattern オブジェクト) を代表 RGBA へ解決する。
132
+ RGBA ColorFromValue(v8::Isolate* iso, v8::Local<v8::Value> value)
133
+ {
134
+ if (value->IsObject()) {
135
+ v8::Local<v8::Object> o = value.As<v8::Object>();
136
+ if (o->InternalFieldCount() >= 1) {
137
+ v8::Local<v8::Value> ext = o->GetInternalField(0).As<v8::Value>();
138
+ if (ext->IsExternal()) {
139
+ auto* paint = static_cast<PaintObject*>(ext.As<v8::External>()->Value());
140
+ if (paint) {
141
+ if (paint->kind == PaintObject::Gradient && !paint->stops.empty()) {
142
+ // 代表色 = 全ストップの平均 (近似)。
143
+ uint32_t r=0,g=0,b=0,al=0;
144
+ for (auto& s : paint->stops) { r+=s.second.r; g+=s.second.g; b+=s.second.b; al+=s.second.a; }
145
+ const uint32_t n = static_cast<uint32_t>(paint->stops.size());
146
+ return RGBA{ uint8_t(r/n), uint8_t(g/n), uint8_t(b/n), uint8_t(al/n) };
147
+ }
148
+ return paint->sample;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ return ParseColor(ToStdString(iso, value));
88
154
  }
89
155
 
90
156
  // --- パス構築 -------------------------------------------------------------
@@ -159,8 +225,9 @@ void Stroke(const v8::FunctionCallbackInfo<v8::Value>& a)
159
225
  void IsPointInPath(const v8::FunctionCallbackInfo<v8::Value>& a)
160
226
  {
161
227
  Canvas2D* c = Self(a.This());
162
- a.GetReturnValue().Set(v8::Boolean::New(
163
- a.GetIsolate(), raster::PointInPath(c->path, Arg(a,0), Arg(a,1))));
228
+ const double px = Arg(a,0), py = Arg(a,1);
229
+ const bool hit = raster::PointInPath(c->path, px, py);
230
+ a.GetReturnValue().Set(v8::Boolean::New(a.GetIsolate(), hit));
164
231
  }
165
232
 
166
233
  // --- 変換 ----------------------------------------------------------------
@@ -201,16 +268,22 @@ void Restore(const v8::FunctionCallbackInfo<v8::Value>& a)
201
268
  Canvas2D* c = Self(a.This());
202
269
  if (!c->stack.empty()) { c->state = c->stack.back(); c->stack.pop_back(); }
203
270
  }
204
- // clip(): 現在パスの外接矩形をクリップ領域として設定する (デバイス座標)。
205
- // 既存クリップとは積集合を取る。player の TextField 枠は矩形パスなので外接矩形で十分。
206
- // «EXTEND» 任意形状の厳密クリップは per-pixel マスクが必要 (現状は矩形近似)。
271
+ // clip(): 現在パスをクリップ領域として設定する (デバイス座標)。既存クリップと積集合。
272
+ // 矩形パス (TextField 枠等) → 外接矩形クリップ (厳密・軽量)
273
+ // 任意形状パス (円/多角形等) → nonzero winding のピクセルマスクで厳密にクリップ
207
274
  void Clip(const v8::FunctionCallbackInfo<v8::Value>& a)
208
275
  {
209
276
  Canvas2D* c = Self(a.This());
210
277
  double minx, miny, maxx, maxy;
211
278
  if (!raster::PathBounds(c->path, &minx, &miny, &maxx, &maxy)) return;
212
- raster::IntersectClip(c->state.clip,
213
- std::floor(minx), std::floor(miny), std::ceil(maxx), std::ceil(maxy));
279
+ if (raster::IsAxisAlignedRect(c->path)) {
280
+ // 矩形クリップは外接矩形で厳密 (軽量)。
281
+ raster::IntersectClip(c->state.clip,
282
+ std::floor(minx), std::floor(miny), std::ceil(maxx), std::ceil(maxy));
283
+ } else {
284
+ // 任意形状はピクセルマスクで厳密にクリップする (外接矩形も内部で反映)。
285
+ raster::IntersectClipMask(c->state.clip, c->path, c->surface.width, c->surface.height);
286
+ }
214
287
  }
215
288
 
216
289
  // --- 矩形 / クリア --------------------------------------------------------
@@ -269,8 +342,26 @@ void RasterizeText(Canvas2D* c, const std::wstring& wtext, double x, double y, c
269
342
  TextBitmap bmp;
270
343
  if (!RasterizeTextWithDWrite(c->state.font, wtext, col.r, col.g, col.b, bmp)) return;
271
344
 
272
- // fillText の x,y はベースライン起点。ビットマップは左上起点なので baseline を引く。
273
- Point origin = Apply(c->state.transform, x, y - bmp.baseline);
345
+ // fillText の x,y はベースライン起点。ビットマップは左上起点。
346
+ Point origin = Apply(c->state.transform, x, y);
347
+ // textAlign (device px 近似): start/left=左, center=中央, right/end=右。
348
+ const std::string& al = c->state.text_align;
349
+ if (al == "center") {
350
+ origin.x -= bmp.width / 2.0;
351
+ } else if (al == "right" || al == "end") {
352
+ origin.x -= bmp.width;
353
+ }
354
+ // textBaseline (device px 近似)。
355
+ const std::string& bl = c->state.text_baseline;
356
+ if (bl == "top" || bl == "hanging") {
357
+ // 上端起点: そのまま
358
+ } else if (bl == "middle") {
359
+ origin.y -= bmp.height / 2.0;
360
+ } else if (bl == "bottom" || bl == "ideographic") {
361
+ origin.y -= bmp.height;
362
+ } else {
363
+ origin.y -= bmp.baseline; // alphabetic (既定)
364
+ }
274
365
  for (int yy = 0; yy < bmp.height; ++yy) {
275
366
  for (int xx = 0; xx < bmp.width; ++xx) {
276
367
  const uint8_t* px = &bmp.rgba[(static_cast<size_t>(yy) * bmp.width + xx) * 4];
@@ -352,47 +443,363 @@ void GetImageData(const v8::FunctionCallbackInfo<v8::Value>& a)
352
443
  a.GetReturnValue().Set(image_data);
353
444
  }
354
445
 
355
- // GC 時に Canvas2D 実体を解放
356
- void ReleaseCanvas2D(const v8::WeakCallbackInfo<Canvas2D>& info) { delete info.GetParameter(); }
446
+ // --- 追加ジオメトリ: strokeRect / arcTo / ellipse / roundRect ---------------
447
+ void StrokeRect(const v8::FunctionCallbackInfo<v8::Value>& a)
448
+ {
449
+ Canvas2D* c = Self(a.This());
450
+ double x = Arg(a,0), y = Arg(a,1), w = Arg(a,2), h = Arg(a,3);
451
+ std::vector<SubPath> tmp; tmp.push_back(SubPath{});
452
+ const double cn[4][2] = { {x,y}, {x+w,y}, {x+w,y+h}, {x,y+h} };
453
+ for (auto& pt : cn) tmp.back().pts.push_back(Apply(c->state.transform, pt[0], pt[1]));
454
+ tmp.back().closed = true;
455
+ raster::StrokePath(c->surface, c->state.clip, tmp, c->state.stroke, c->state.global_alpha);
456
+ }
457
+
458
+ void ArcTo(const v8::FunctionCallbackInfo<v8::Value>& a)
459
+ {
460
+ Canvas2D* c = Self(a.This());
461
+ const double x1 = Arg(a,0), y1 = Arg(a,1), x2 = Arg(a,2), y2 = Arg(a,3), r = Arg(a,4);
462
+ const double x0 = c->cur_x, y0 = c->cur_y;
463
+ const double a1 = x0-x1, b1 = y0-y1, a2 = x2-x1, b2 = y2-y1;
464
+ const double l1 = std::hypot(a1,b1), l2 = std::hypot(a2,b2);
465
+ if (l1 < 1e-6 || l2 < 1e-6 || r < 1e-6) { AddPoint(c, x1, y1, c->path.empty()); return; }
466
+ double cosang = std::clamp((a1*a2 + b1*b2) / (l1*l2), -1.0, 1.0);
467
+ const double ang = std::acos(cosang);
468
+ const double th = std::tan(ang / 2);
469
+ if (th < 1e-6) { AddPoint(c, x1, y1, c->path.empty()); return; }
470
+ const double dist = r / th;
471
+ const double t1x = x1 + a1/l1*dist, t1y = y1 + b1/l1*dist;
472
+ const double t2x = x1 + a2/l2*dist, t2y = y1 + b2/l2*dist;
473
+ AddPoint(c, t1x, t1y, c->path.empty()); // 現在点→第一接点
474
+ double bx = a1/l1 + a2/l2, by = b1/l1 + b2/l2;
475
+ const double bl = std::hypot(bx, by);
476
+ if (bl > 1e-6) {
477
+ const double cenDist = r / std::sin(ang / 2);
478
+ const double cx = x1 + bx/bl*cenDist, cy = y1 + by/bl*cenDist;
479
+ const double sa = std::atan2(t1y-cy, t1x-cx), ea = std::atan2(t2y-cy, t2x-cx);
480
+ const bool ccw = (a1*b2 - b1*a2) > 0;
481
+ raster::FlattenArc(c->path.back().pts, c->state.transform, cx, cy, r, sa, ea, ccw);
482
+ }
483
+ c->cur_x = t2x; c->cur_y = t2y;
484
+ }
485
+
486
+ void Ellipse(const v8::FunctionCallbackInfo<v8::Value>& a)
487
+ {
488
+ Canvas2D* c = Self(a.This());
489
+ const double cx = Arg(a,0), cy = Arg(a,1), rx = Arg(a,2), ry = Arg(a,3), rot = Arg(a,4);
490
+ double s = Arg(a,5), e = Arg(a,6, 2*raster::kPi);
491
+ const bool ccw = a.Length() > 7 && a[7]->BooleanValue(a.GetIsolate());
492
+ if (ccw && e > s) e -= 2*raster::kPi;
493
+ if (!ccw && e < s) e += 2*raster::kPi;
494
+ if (c->path.empty()) c->path.push_back(SubPath{});
495
+ const double cr = std::cos(rot), sr = std::sin(rot);
496
+ const int steps = 48;
497
+ for (int i = 0; i <= steps; ++i) {
498
+ const double t = s + (e - s) * (static_cast<double>(i) / steps);
499
+ const double ex = rx*std::cos(t), ey = ry*std::sin(t);
500
+ c->path.back().pts.push_back(
501
+ Apply(c->state.transform, cx + ex*cr - ey*sr, cy + ex*sr + ey*cr));
502
+ }
503
+ }
357
504
 
358
- // fillStyle / strokeStyle / lineWidth / globalAlpha / font アクセサ
505
+ void RoundRect(const v8::FunctionCallbackInfo<v8::Value>& a)
506
+ {
507
+ Canvas2D* c = Self(a.This());
508
+ const double x = Arg(a,0), y = Arg(a,1), w = Arg(a,2), h = Arg(a,3);
509
+ double r = 0.0;
510
+ if (a.Length() > 4) {
511
+ if (a[4]->IsNumber()) {
512
+ r = a[4].As<v8::Number>()->Value();
513
+ } else if (a[4]->IsArray()) {
514
+ auto arr = a[4].As<v8::Array>();
515
+ v8::Local<v8::Value> v0;
516
+ if (arr->Length() > 0 && arr->Get(a.GetIsolate()->GetCurrentContext(), 0).ToLocal(&v0) && v0->IsNumber()) {
517
+ r = v0.As<v8::Number>()->Value();
518
+ }
519
+ }
520
+ }
521
+ r = std::min(r, std::min(std::abs(w), std::abs(h)) / 2.0);
522
+ c->path.push_back(SubPath{});
523
+ auto& pts = c->path.back().pts;
524
+ const double hp = raster::kPi / 2;
525
+ raster::FlattenArc(pts, c->state.transform, x+w-r, y+r, r, -hp, 0.0, false); // 右上
526
+ raster::FlattenArc(pts, c->state.transform, x+w-r, y+h-r, r, 0.0, hp, false); // 右下
527
+ raster::FlattenArc(pts, c->state.transform, x+r, y+h-r, r, hp, raster::kPi, false); // 左下
528
+ raster::FlattenArc(pts, c->state.transform, x+r, y+r, r, raster::kPi, hp*3, false); // 左上
529
+ c->path.back().closed = true;
530
+ }
531
+
532
+ // --- 追加変換: resetTransform / getTransform --------------------------------
533
+ void ResetTransform(const v8::FunctionCallbackInfo<v8::Value>& a)
534
+ {
535
+ Self(a.This())->state.transform = Mat{1,0,0,1,0,0};
536
+ }
537
+ void GetTransform(const v8::FunctionCallbackInfo<v8::Value>& a)
538
+ {
539
+ v8::Isolate* iso = a.GetIsolate();
540
+ const Mat& m = Self(a.This())->state.transform;
541
+ v8::Local<v8::Object> o = v8::Object::New(iso);
542
+ SetValue(iso, o, "a", v8::Number::New(iso, m.a));
543
+ SetValue(iso, o, "b", v8::Number::New(iso, m.b));
544
+ SetValue(iso, o, "c", v8::Number::New(iso, m.c));
545
+ SetValue(iso, o, "d", v8::Number::New(iso, m.d));
546
+ SetValue(iso, o, "e", v8::Number::New(iso, m.e));
547
+ SetValue(iso, o, "f", v8::Number::New(iso, m.f));
548
+ // DOMMatrix 互換の別名も付ける
549
+ SetValue(iso, o, "m11", v8::Number::New(iso, m.a));
550
+ SetValue(iso, o, "m12", v8::Number::New(iso, m.b));
551
+ SetValue(iso, o, "m21", v8::Number::New(iso, m.c));
552
+ SetValue(iso, o, "m22", v8::Number::New(iso, m.d));
553
+ SetValue(iso, o, "m41", v8::Number::New(iso, m.e));
554
+ SetValue(iso, o, "m42", v8::Number::New(iso, m.f));
555
+ a.GetReturnValue().Set(o);
556
+ }
557
+
558
+ // --- 追加ライン: getLineDash / isPointInStroke -----------------------------
559
+ void GetLineDash(const v8::FunctionCallbackInfo<v8::Value>& a)
560
+ {
561
+ v8::Isolate* iso = a.GetIsolate();
562
+ v8::Local<v8::Context> ctx = iso->GetCurrentContext();
563
+ const auto& dash = Self(a.This())->state.line_dash;
564
+ v8::Local<v8::Array> arr = v8::Array::New(iso, static_cast<int>(dash.size()));
565
+ for (uint32_t i = 0; i < dash.size(); ++i) {
566
+ arr->Set(ctx, i, v8::Number::New(iso, dash[i])).Check();
567
+ }
568
+ a.GetReturnValue().Set(arr);
569
+ }
570
+ // isPointInStroke: 近似としてパス内包 (fill) と同じ判定を返す。
571
+ void IsPointInStroke(const v8::FunctionCallbackInfo<v8::Value>& a)
572
+ {
573
+ Canvas2D* c = Self(a.This());
574
+ a.GetReturnValue().Set(v8::Boolean::New(
575
+ a.GetIsolate(), raster::PointInPath(c->path, Arg(a,0), Arg(a,1))));
576
+ }
577
+
578
+ // --- 追加: gradient / pattern ----------------------------------------------
579
+ void AddColorStop(const v8::FunctionCallbackInfo<v8::Value>& a)
580
+ {
581
+ v8::Local<v8::Object> self = a.This();
582
+ if (self->InternalFieldCount() < 1) return;
583
+ v8::Local<v8::Value> ext = self->GetInternalField(0).As<v8::Value>();
584
+ if (!ext->IsExternal()) return;
585
+ auto* p = static_cast<PaintObject*>(ext.As<v8::External>()->Value());
586
+ if (!p) return;
587
+ p->stops.push_back({ Arg(a,0), ParseColor(ToStdString(a.GetIsolate(), a.Length()>1 ? a[1] : v8::Local<v8::Value>())) });
588
+ }
589
+
590
+ v8::Local<v8::Object> MakePaintObject(v8::Isolate* isolate, PaintObject* paint)
591
+ {
592
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
593
+ v8::Local<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New(isolate);
594
+ tmpl->SetInternalFieldCount(1);
595
+ v8::Local<v8::Object> o = tmpl->NewInstance(ctx).ToLocalChecked();
596
+ o->SetInternalField(0, v8::External::New(isolate, paint));
597
+ v8util::AttachWeak(isolate, o, paint);
598
+ SetMethod(isolate, o, "addColorStop", AddColorStop);
599
+ return o;
600
+ }
601
+
602
+ void CreateLinearGradient(const v8::FunctionCallbackInfo<v8::Value>& a)
603
+ {
604
+ auto* p = new PaintObject();
605
+ p->kind = PaintObject::Gradient;
606
+ a.GetReturnValue().Set(MakePaintObject(a.GetIsolate(), p));
607
+ }
608
+ void CreateRadialGradient(const v8::FunctionCallbackInfo<v8::Value>& a)
609
+ {
610
+ auto* p = new PaintObject();
611
+ p->kind = PaintObject::Gradient;
612
+ a.GetReturnValue().Set(MakePaintObject(a.GetIsolate(), p));
613
+ }
614
+ void CreateConicGradient(const v8::FunctionCallbackInfo<v8::Value>& a)
615
+ {
616
+ auto* p = new PaintObject();
617
+ p->kind = PaintObject::Gradient;
618
+ a.GetReturnValue().Set(MakePaintObject(a.GetIsolate(), p));
619
+ }
620
+ void CreatePattern(const v8::FunctionCallbackInfo<v8::Value>& a)
621
+ {
622
+ v8::Isolate* iso = a.GetIsolate();
623
+ auto* p = new PaintObject();
624
+ p->kind = PaintObject::Pattern;
625
+ // 画像の代表色 (中央付近の平均) をサンプルする。
626
+ const uint8_t* src = nullptr; uint32_t sw = 0, sh = 0;
627
+ if (a.Length() > 0 && GetImageSourcePixels(iso, a[0], &src, &sw, &sh) && src && sw && sh) {
628
+ uint64_t r=0,g=0,b=0,al=0,n=0;
629
+ for (uint32_t yy = 0; yy < sh; yy += std::max(1u, sh/16)) {
630
+ for (uint32_t xx = 0; xx < sw; xx += std::max(1u, sw/16)) {
631
+ const uint8_t* px = &src[(static_cast<size_t>(yy)*sw + xx)*4];
632
+ r+=px[0]; g+=px[1]; b+=px[2]; al+=px[3]; ++n;
633
+ }
634
+ }
635
+ if (n) p->sample = RGBA{ uint8_t(r/n), uint8_t(g/n), uint8_t(b/n), uint8_t(al/n) };
636
+ }
637
+ a.GetReturnValue().Set(MakePaintObject(iso, p));
638
+ }
639
+
640
+ // --- 追加: ImageData 生成 / 書き戻し ---------------------------------------
641
+ void CreateImageData(const v8::FunctionCallbackInfo<v8::Value>& a)
642
+ {
643
+ v8::Isolate* iso = a.GetIsolate();
644
+ v8::Local<v8::Context> ctx = iso->GetCurrentContext();
645
+ int w = 0, h = 0;
646
+ if (a.Length() > 0 && a[0]->IsObject() && !a[0]->IsNumber()) {
647
+ // createImageData(imagedata): 同サイズの空データ
648
+ v8::Local<v8::Object> o = a[0].As<v8::Object>();
649
+ v8::Local<v8::Value> wv, hv;
650
+ if (o->Get(ctx, Str(iso, "width")).ToLocal(&wv)) w = static_cast<int>(wv->NumberValue(ctx).FromMaybe(0));
651
+ if (o->Get(ctx, Str(iso, "height")).ToLocal(&hv)) h = static_cast<int>(hv->NumberValue(ctx).FromMaybe(0));
652
+ } else {
653
+ w = std::abs(static_cast<int>(Arg(a,0)));
654
+ h = std::abs(static_cast<int>(Arg(a,1)));
655
+ }
656
+ if (w <= 0) w = 1;
657
+ if (h <= 0) h = 1;
658
+ const size_t bytes = static_cast<size_t>(w) * h * 4;
659
+ auto store = v8::ArrayBuffer::NewBackingStore(iso, bytes); // 0 初期化
660
+ auto ab = v8::ArrayBuffer::New(iso, std::move(store));
661
+ auto arr = v8::Uint8ClampedArray::New(ab, 0, bytes);
662
+ v8::Local<v8::Object> image_data = v8::Object::New(iso);
663
+ SetValue(iso, image_data, "width", v8::Integer::New(iso, w));
664
+ SetValue(iso, image_data, "height", v8::Integer::New(iso, h));
665
+ image_data->Set(ctx, Str(iso, "data"), arr).Check();
666
+ a.GetReturnValue().Set(image_data);
667
+ }
668
+
669
+ void PutImageData(const v8::FunctionCallbackInfo<v8::Value>& a)
670
+ {
671
+ v8::Isolate* iso = a.GetIsolate();
672
+ v8::Local<v8::Context> ctx = iso->GetCurrentContext();
673
+ Canvas2D* c = Self(a.This());
674
+ if (a.Length() < 3 || !a[0]->IsObject()) return;
675
+ v8::Local<v8::Object> o = a[0].As<v8::Object>();
676
+ v8::Local<v8::Value> wv, hv, dv;
677
+ if (!o->Get(ctx, Str(iso, "width")).ToLocal(&wv) ||
678
+ !o->Get(ctx, Str(iso, "height")).ToLocal(&hv) ||
679
+ !o->Get(ctx, Str(iso, "data")).ToLocal(&dv) || !dv->IsTypedArray()) {
680
+ return;
681
+ }
682
+ const int iw = static_cast<int>(wv->NumberValue(ctx).FromMaybe(0));
683
+ const int ih = static_cast<int>(hv->NumberValue(ctx).FromMaybe(0));
684
+ auto ta = dv.As<v8::TypedArray>();
685
+ auto ab = ta->Buffer();
686
+ const uint8_t* data = static_cast<const uint8_t*>(ab->Data()) + ta->ByteOffset();
687
+ const int dx = static_cast<int>(Arg(a,1)), dy = static_cast<int>(Arg(a,2));
688
+ // putImageData は変換・クリップ非適用でそのまま書き込む (Canvas 仕様)。
689
+ for (int yy = 0; yy < ih; ++yy) {
690
+ for (int xx = 0; xx < iw; ++xx) {
691
+ const int sx = dx + xx, sy = dy + yy;
692
+ if (sx < 0 || sy < 0 || sx >= c->surface.width || sy >= c->surface.height) continue;
693
+ const size_t si = (static_cast<size_t>(yy)*iw + xx) * 4;
694
+ const size_t di = (static_cast<size_t>(sy)*c->surface.width + sx) * 4;
695
+ std::memcpy(&c->surface.pixels[di], &data[si], 4);
696
+ }
697
+ }
698
+ }
699
+
700
+ // --- 追加: reset() ----------------------------------------------------------
701
+ void Reset(const v8::FunctionCallbackInfo<v8::Value>& a)
702
+ {
703
+ Canvas2D* c = Self(a.This());
704
+ c->path.clear();
705
+ c->stack.clear();
706
+ c->state = State{};
707
+ std::fill(c->surface.pixels.begin(), c->surface.pixels.end(), static_cast<uint8_t>(0));
708
+ }
709
+
710
+ // CanvasRenderingContext2D の全プロパティを getter/setter 付きで設置する。
711
+ // 解釈して描画へ反映するもの(fill/stroke/font/textAlign/textBaseline/lineWidth/
712
+ // globalAlpha)と、状態として往復保持するだけのもの(shadow/composite/filter 等の
713
+ // 近似)を含む。全て state に持たせるため save()/restore() でも復元される。
359
714
  void InstallAccessors(v8::Isolate* isolate, v8::Local<v8::Object> obj)
360
715
  {
361
716
  v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
362
- auto set_str = [&](const char* name, void(*setter)(Canvas2D*, const std::string&)) {
363
- obj->SetNativeDataProperty(ctx, Str(isolate, name),
364
- nullptr,
365
- [](v8::Local<v8::Name>, v8::Local<v8::Value> value,
366
- const v8::PropertyCallbackInfo<void>& info) {
367
- // setter は External(data) 経由で受け取る
368
- auto* fn = reinterpret_cast<void(*)(Canvas2D*, const std::string&)>(
369
- info.Data().As<v8::External>()->Value());
370
- fn(Self(info.This()), ToStdString(info.GetIsolate(), value));
371
- },
372
- v8::External::New(isolate, reinterpret_cast<void*>(setter))).Check();
373
- };
374
- // fillStyle は player が色文字列の正規化に「代入後の読み出し」を使うため getter も持たせる。
717
+
718
+ // state の string/number/bool メンバへ往復するアクセサを設置するマクロ。
719
+ #define N2D_STR_PROP(NAME, MEMBER) \
720
+ obj->SetNativeDataProperty(ctx, Str(isolate, NAME), \
721
+ [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info){ \
722
+ info.GetReturnValue().Set(v8util::Str(info.GetIsolate(), Self(info.This())->state.MEMBER)); }, \
723
+ [](v8::Local<v8::Name>, v8::Local<v8::Value> v, const v8::PropertyCallbackInfo<void>& info){ \
724
+ Self(info.This())->state.MEMBER = ToStdString(info.GetIsolate(), v); }).Check()
725
+ #define N2D_NUM_PROP(NAME, MEMBER) \
726
+ obj->SetNativeDataProperty(ctx, Str(isolate, NAME), \
727
+ [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info){ \
728
+ info.GetReturnValue().Set(v8::Number::New(info.GetIsolate(), Self(info.This())->state.MEMBER)); }, \
729
+ [](v8::Local<v8::Name>, v8::Local<v8::Value> v, const v8::PropertyCallbackInfo<void>& info){ \
730
+ if (v->IsNumber()) Self(info.This())->state.MEMBER = v.As<v8::Number>()->Value(); }).Check()
731
+ #define N2D_BOOL_PROP(NAME, MEMBER) \
732
+ obj->SetNativeDataProperty(ctx, Str(isolate, NAME), \
733
+ [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info){ \
734
+ info.GetReturnValue().Set(v8::Boolean::New(info.GetIsolate(), Self(info.This())->state.MEMBER)); }, \
735
+ [](v8::Local<v8::Name>, v8::Local<v8::Value> v, const v8::PropertyCallbackInfo<void>& info){ \
736
+ Self(info.This())->state.MEMBER = v->BooleanValue(info.GetIsolate()); }).Check()
737
+
738
+ // fillStyle / strokeStyle: gradient/pattern オブジェクトも受け取り代表色へ解決。
739
+ // 重要: getter は「代入した色を #rrggbb に正規化して」返す (ブラウザ準拠)。
740
+ // player は色文字列の数値化に読み戻しを使う (@next2d/filters の
741
+ // $convertColorStringToNumber は `+("0x"+ctx.fillStyle.slice(1))` で解釈するため、
742
+ // getter が "rgb(191,255,255)" 等の生文字列を返すと NaN になり、その色を使う
743
+ // グラデーション (スライム body 等) が黒くなる)。パース済み RGBA を hex 化して保持する。
375
744
  obj->SetNativeDataProperty(ctx, Str(isolate, "fillStyle"),
376
745
  [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
377
- RGBA c = Self(info.This())->state.fill;
378
- char buf[8];
379
- std::snprintf(buf, sizeof(buf), "#%02x%02x%02x", c.r, c.g, c.b);
380
- info.GetReturnValue().Set(v8util::Str(info.GetIsolate(), buf));
746
+ info.GetReturnValue().Set(v8util::Str(info.GetIsolate(), Self(info.This())->state.fill_style_str));
381
747
  },
382
748
  [](v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {
383
- Self(info.This())->state.fill = ParseColor(ToStdString(info.GetIsolate(), value));
384
- }).Check();
385
- set_str("strokeStyle", [](Canvas2D* c, const std::string& s){ c->state.stroke = ParseColor(s); });
386
- set_str("font", [](Canvas2D* c, const std::string& s){ c->state.font = s; });
387
-
388
- obj->SetNativeDataProperty(ctx, Str(isolate, "lineWidth"), nullptr,
389
- [](v8::Local<v8::Name>, v8::Local<v8::Value> v, const v8::PropertyCallbackInfo<void>& info){
390
- if (v->IsNumber()) Self(info.This())->state.line_width = v.As<v8::Number>()->Value();
749
+ Canvas2D* c = Self(info.This());
750
+ c->state.fill = ColorFromValue(info.GetIsolate(), value);
751
+ if (value->IsString()) {
752
+ char hex[8];
753
+ std::snprintf(hex, sizeof(hex), "#%02x%02x%02x",
754
+ static_cast<unsigned>(c->state.fill.r),
755
+ static_cast<unsigned>(c->state.fill.g),
756
+ static_cast<unsigned>(c->state.fill.b));
757
+ c->state.fill_style_str = hex;
758
+ }
391
759
  }).Check();
392
- obj->SetNativeDataProperty(ctx, Str(isolate, "globalAlpha"), nullptr,
393
- [](v8::Local<v8::Name>, v8::Local<v8::Value> v, const v8::PropertyCallbackInfo<void>& info){
394
- if (v->IsNumber()) Self(info.This())->state.global_alpha = v.As<v8::Number>()->Value();
760
+ obj->SetNativeDataProperty(ctx, Str(isolate, "strokeStyle"),
761
+ [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
762
+ info.GetReturnValue().Set(v8util::Str(info.GetIsolate(), Self(info.This())->state.stroke_style_str));
763
+ },
764
+ [](v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {
765
+ Canvas2D* c = Self(info.This());
766
+ c->state.stroke = ColorFromValue(info.GetIsolate(), value);
767
+ if (value->IsString()) {
768
+ char hex[8];
769
+ std::snprintf(hex, sizeof(hex), "#%02x%02x%02x",
770
+ static_cast<unsigned>(c->state.stroke.r),
771
+ static_cast<unsigned>(c->state.stroke.g),
772
+ static_cast<unsigned>(c->state.stroke.b));
773
+ c->state.stroke_style_str = hex;
774
+ }
395
775
  }).Check();
776
+
777
+ N2D_STR_PROP("font", font);
778
+ N2D_NUM_PROP("lineWidth", line_width);
779
+ N2D_NUM_PROP("globalAlpha", global_alpha);
780
+ N2D_STR_PROP("lineCap", line_cap);
781
+ N2D_STR_PROP("lineJoin", line_join);
782
+ N2D_NUM_PROP("miterLimit", miter_limit);
783
+ N2D_NUM_PROP("lineDashOffset", line_dash_offset);
784
+ N2D_NUM_PROP("shadowBlur", shadow_blur);
785
+ N2D_STR_PROP("shadowColor", shadow_color);
786
+ N2D_NUM_PROP("shadowOffsetX", shadow_offset_x);
787
+ N2D_NUM_PROP("shadowOffsetY", shadow_offset_y);
788
+ N2D_STR_PROP("textAlign", text_align);
789
+ N2D_STR_PROP("textBaseline", text_baseline);
790
+ N2D_STR_PROP("direction", direction);
791
+ N2D_STR_PROP("globalCompositeOperation", global_composite_operation);
792
+ N2D_BOOL_PROP("imageSmoothingEnabled", image_smoothing_enabled);
793
+ N2D_STR_PROP("imageSmoothingQuality", image_smoothing_quality);
794
+ N2D_STR_PROP("filter", filter);
795
+ N2D_STR_PROP("fontKerning", font_kerning);
796
+ N2D_STR_PROP("letterSpacing", letter_spacing);
797
+ N2D_STR_PROP("wordSpacing", word_spacing);
798
+ N2D_STR_PROP("textRendering", text_rendering);
799
+
800
+ #undef N2D_STR_PROP
801
+ #undef N2D_NUM_PROP
802
+ #undef N2D_BOOL_PROP
396
803
  }
397
804
 
398
805
  } // namespace
@@ -424,9 +831,9 @@ v8::Local<v8::Object> CreateCanvas2DContext(v8::Isolate* isolate, HostContext* /
424
831
 
425
832
  auto* impl = new Canvas2D(width > 0 ? width : 1, height > 0 ? height : 1);
426
833
  obj->SetInternalField(0, v8::External::New(isolate, impl));
427
- auto* handle = new v8::Global<v8::Object>(isolate, obj);
428
- handle->SetWeak(impl, ReleaseCanvas2D, v8::WeakCallbackType::kParameter);
834
+ v8util::AttachWeak(isolate, obj, impl);
429
835
 
836
+ // パス構築
430
837
  SetMethod(isolate, obj, "beginPath", BeginPath);
431
838
  SetMethod(isolate, obj, "moveTo", MoveTo);
432
839
  SetMethod(isolate, obj, "lineTo", LineTo);
@@ -434,26 +841,72 @@ v8::Local<v8::Object> CreateCanvas2DContext(v8::Isolate* isolate, HostContext* /
434
841
  SetMethod(isolate, obj, "bezierCurveTo", BezierCurveTo);
435
842
  SetMethod(isolate, obj, "quadraticCurveTo", QuadraticCurveTo);
436
843
  SetMethod(isolate, obj, "arc", Arc);
844
+ SetMethod(isolate, obj, "arcTo", ArcTo);
845
+ SetMethod(isolate, obj, "ellipse", Ellipse);
437
846
  SetMethod(isolate, obj, "rect", Rect);
847
+ SetMethod(isolate, obj, "roundRect", RoundRect);
848
+ // 塗り/線/判定
438
849
  SetMethod(isolate, obj, "fill", Fill);
439
850
  SetMethod(isolate, obj, "stroke", Stroke);
440
851
  SetMethod(isolate, obj, "clip", Clip);
441
852
  SetMethod(isolate, obj, "isPointInPath", IsPointInPath);
853
+ SetMethod(isolate, obj, "isPointInStroke", IsPointInStroke);
854
+ // 変換
442
855
  SetMethod(isolate, obj, "setTransform", SetTransform);
443
856
  SetMethod(isolate, obj, "transform", Transform);
444
857
  SetMethod(isolate, obj, "translate", Translate);
445
858
  SetMethod(isolate, obj, "scale", Scale);
446
859
  SetMethod(isolate, obj, "rotate", Rotate);
860
+ SetMethod(isolate, obj, "resetTransform", ResetTransform);
861
+ SetMethod(isolate, obj, "getTransform", GetTransform);
862
+ // 状態スタック
447
863
  SetMethod(isolate, obj, "save", Save);
448
864
  SetMethod(isolate, obj, "restore", Restore);
865
+ SetMethod(isolate, obj, "reset", Reset);
866
+ // 矩形
449
867
  SetMethod(isolate, obj, "clearRect", ClearRect);
450
868
  SetMethod(isolate, obj, "fillRect", FillRect);
869
+ SetMethod(isolate, obj, "strokeRect", StrokeRect);
870
+ // テキスト
451
871
  SetMethod(isolate, obj, "measureText", MeasureText);
452
872
  SetMethod(isolate, obj, "fillText", FillText);
453
873
  SetMethod(isolate, obj, "strokeText", StrokeText);
874
+ // 画像 / ImageData
454
875
  SetMethod(isolate, obj, "drawImage", DrawImage);
455
876
  SetMethod(isolate, obj, "getImageData", GetImageData);
456
- SetMethod(isolate, obj, "setLineDash", [](const v8::FunctionCallbackInfo<v8::Value>&) {});
877
+ SetMethod(isolate, obj, "createImageData", CreateImageData);
878
+ SetMethod(isolate, obj, "putImageData", PutImageData);
879
+ // gradient / pattern
880
+ SetMethod(isolate, obj, "createLinearGradient", CreateLinearGradient);
881
+ SetMethod(isolate, obj, "createRadialGradient", CreateRadialGradient);
882
+ SetMethod(isolate, obj, "createConicGradient", CreateConicGradient);
883
+ SetMethod(isolate, obj, "createPattern", CreatePattern);
884
+ // 線種
885
+ SetMethod(isolate, obj, "getLineDash", GetLineDash);
886
+ SetMethod(isolate, obj, "setLineDash", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
887
+ Canvas2D* c = Self(a.This());
888
+ c->state.line_dash.clear();
889
+ if (a.Length() > 0 && a[0]->IsArray()) {
890
+ auto arr = a[0].As<v8::Array>();
891
+ v8::Local<v8::Context> ctx2 = a.GetIsolate()->GetCurrentContext();
892
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
893
+ v8::Local<v8::Value> v;
894
+ if (arr->Get(ctx2, i).ToLocal(&v) && v->IsNumber()) {
895
+ c->state.line_dash.push_back(v.As<v8::Number>()->Value());
896
+ }
897
+ }
898
+ }
899
+ });
900
+ // 参考: getContextAttributes (最小)
901
+ SetMethod(isolate, obj, "getContextAttributes", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
902
+ v8::Isolate* iso = a.GetIsolate();
903
+ v8::Local<v8::Object> o = v8::Object::New(iso);
904
+ SetValue(iso, o, "alpha", v8::Boolean::New(iso, true));
905
+ SetValue(iso, o, "desynchronized", v8::Boolean::New(iso, false));
906
+ SetValue(iso, o, "colorSpace", v8util::Str(iso, "srgb"));
907
+ SetValue(iso, o, "willReadFrequently", v8::Boolean::New(iso, false));
908
+ a.GetReturnValue().Set(o);
909
+ });
457
910
 
458
911
  InstallAccessors(isolate, obj);
459
912
  return obj;
@@ -30,7 +30,12 @@ void Log(const v8::FunctionCallbackInfo<v8::Value>& args)
30
30
 
31
31
  void Error(const v8::FunctionCallbackInfo<v8::Value>& args)
32
32
  {
33
- std::cerr << FormatArgs(args.GetIsolate(), args) << std::endl;
33
+ // console.error / console.warn は GUI 実行では stderr が見えないため、
34
+ // next2d-error.log にもミラーする (player 側のエラー・警告や WebGPU の
35
+ // error scope 結果などを掴む)。
36
+ const std::string msg = FormatArgs(args.GetIsolate(), args);
37
+ std::cerr << msg << std::endl;
38
+ v8util::AppendErrorLog("[console] " + msg);
34
39
  }
35
40
 
36
41
  } // namespace