@geastack/core 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/events.cpp CHANGED
@@ -46,6 +46,8 @@ const char *PointerEvent::typeName() const
46
46
  return "click";
47
47
  case PointerEventType::Input:
48
48
  return "input";
49
+ case PointerEventType::KeyUp:
50
+ return "keyup";
49
51
  case PointerEventType::KeyDown:
50
52
  return "keydown";
51
53
  case PointerEventType::Rotary:
package/gea_app_entry.cpp CHANGED
@@ -268,7 +268,7 @@ void Application::frame(int timestampMs)
268
268
  applicationFramePhaseSet(ApplicationFramePhase::DrainMicrotasks);
269
269
  generated::drainMicrotasks();
270
270
  applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
271
- gea::host::runAnimationFrameCallbacks(static_cast<gea::host::AnimationFrameTimestamp>(timestampMs));
271
+ gea::host::runAnimationFrameCallbacks(timestampMs);
272
272
  generated::drainMicrotasks();
273
273
  applicationFramePhaseSet(ApplicationFramePhase::Idle);
274
274
  #else
@@ -294,7 +294,7 @@ void Application::frame(int timestampMs)
294
294
 
295
295
  startUs = phaseClockUs();
296
296
  applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
297
- gea::host::runAnimationFrameCallbacks(static_cast<double>(timestampMs));
297
+ gea::host::runAnimationFrameCallbacks(timestampMs);
298
298
  applicationFramePhaseSet(ApplicationFramePhase::CallbackMicrotasks);
299
299
  generated::drainMicrotasks();
300
300
  recordFramePhase(ApplicationFramePhase::AnimationFrameCallbacks, phaseClockUs() - startUs);
package/gea_sources.mjs CHANGED
@@ -59,7 +59,8 @@ export function includeFlags(env = process.env) {
59
59
  join(elements, 'ui'),
60
60
  geaos,
61
61
  join(engine, 'vendor/stb'),
62
- join(engine, 'vendor/AnimatedGIF')
62
+ join(engine, 'vendor/AnimatedGIF'),
63
+ ...(env.GEA_COMPILER_RUNTIME ? [env.GEA_COMPILER_RUNTIME] : [])
63
64
  ].map((dir) => `-I${dir}`)
64
65
  }
65
66
 
@@ -87,8 +88,8 @@ export function cxxSources(env = process.env) {
87
88
  // @geastack/host — platform layer + services
88
89
  ...cpp(host, ['input', 'wifi', 'bluetooth', 'geolocation']),
89
90
  ...cpp(join(host, 'host'), [
90
- 'timers', 'worker', 'input', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
91
- 'ble', 'apps', 'audio', 'audio_worklet', 'video', 'mjpeg', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
91
+ 'timers', 'worker', 'input', 'device', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
92
+ 'ble', 'apps', 'audio', 'audio_worklet', 'audio_buffer_runtime', 'video', 'mjpeg', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
92
93
  'tile_loader'
93
94
  ]),
94
95
  // framework services (built on the platform abstraction — no target-specific deps)
package/include/audio.h CHANGED
@@ -7,8 +7,18 @@
7
7
  #include <string>
8
8
  #include <functional>
9
9
 
10
+ // Physical capture/mixer clock shared by the board driver and native hosts.
11
+ // AudioContext and network PCM may use other rates through their resamplers.
12
+ #ifndef GEA_AUDIO_DEVICE_SAMPLE_RATE
13
+ #define GEA_AUDIO_DEVICE_SAMPLE_RATE 16000
14
+ #endif
15
+
10
16
  namespace gea::platform::audio {
11
17
 
18
+ inline constexpr int deviceSampleRate = GEA_AUDIO_DEVICE_SAMPLE_RATE;
19
+ static_assert(deviceSampleRate >= 8000 && deviceSampleRate <= 48000,
20
+ "Audio device sample rate must be between 8000 and 48000 Hz");
21
+
12
22
  enum class OscillatorType {
13
23
  Sine = 0,
14
24
  Square = 1,
@@ -74,7 +84,7 @@ public:
74
84
  static bool playPcm(const std::int16_t *samples, std::size_t sample_count, int sample_rate, int channels);
75
85
  static void stopPlayback();
76
86
  static void flushPlayback();
77
- // Continuous 16 kHz mono source; zero means the platform could not attach it.
87
+ // Continuous mono source at deviceSampleRate; zero means the platform could not attach it.
78
88
  static std::uint64_t playPcmStream(std::function<std::size_t(std::int16_t*, std::size_t)> pull);
79
89
  static void stopPcmStream(std::uint64_t stream);
80
90
  static bool pcmStreamSettled(std::uint64_t stream);
@@ -0,0 +1,52 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <algorithm>
5
+ #include <optional>
6
+
7
+ namespace gea::platform::audio {
8
+
9
+ // Worklet render time and the physical output scheduler can have different
10
+ // origins. Translate an explicit context deadline at the host boundary.
11
+ inline double physicalAudioDeadline(double when, double contextNow,
12
+ double physicalNow) {
13
+ return when > 0 ? physicalNow + when - contextNow : when;
14
+ }
15
+
16
+ struct FiniteToneTiming {
17
+ int durationMs;
18
+ int delayMs;
19
+ };
20
+
21
+ // The embedded oscillator backend schedules one finite tone when stop() is
22
+ // first called. It does not support replacing/cancelling an already queued
23
+ // individual voice. Further stops must never submit that voice a second time.
24
+ class FiniteOscillatorSchedule {
25
+ public:
26
+ void start(double when, double now) {
27
+ startTime_ = when > 0 ? when : now;
28
+ started_ = true;
29
+ submitted_ = false;
30
+ }
31
+
32
+ std::optional<FiniteToneTiming> stop(double when, double now) {
33
+ if (submitted_)
34
+ return std::nullopt;
35
+ submitted_ = true;
36
+ const double start = started_ ? startTime_ : now;
37
+ const double end = std::max(when > 0 ? when : now, start);
38
+ const int duration =
39
+ static_cast<int>(std::min((end - start) * 1000.0 + 0.5, 2500.0));
40
+ if (duration <= 0)
41
+ return std::nullopt;
42
+ return FiniteToneTiming{
43
+ duration, static_cast<int>(std::max(start - now, 0.0) * 1000.0 + 0.5)};
44
+ }
45
+
46
+ private:
47
+ double startTime_ = 0;
48
+ bool started_ = false;
49
+ bool submitted_ = false;
50
+ };
51
+
52
+ } // namespace gea::platform::audio
package/include/camera.h CHANGED
@@ -3,6 +3,7 @@
3
3
  #include "pixel.h"
4
4
 
5
5
  #include <cstdint>
6
+ #include <atomic>
6
7
  #include <string>
7
8
 
8
9
  namespace gea::platform::camera {
@@ -20,6 +21,14 @@ struct DeviceInfo {
20
21
  int sensorHeight = 0;
21
22
  };
22
23
 
24
+ // Optional encoded-frame producer. The device owns capture/encoding on its
25
+ // worker; reading a frame never waits for a sensor or codec on the UI thread.
26
+ class CameraFrameProvider {
27
+ public:
28
+ virtual ~CameraFrameProvider() = default;
29
+ virtual std::string takeFrameDataUrl() = 0;
30
+ };
31
+
23
32
  // Static facade — one active camera at a time on the device. The platform
24
33
  // layer maintains the live preview buffer and exposes a small surface that
25
34
  // the host bridge forwards into.
@@ -36,6 +45,12 @@ public:
36
45
  static void close();
37
46
  static bool isOpen();
38
47
 
48
+ static void setFrameProvider(CameraFrameProvider *provider) { frameProvider().store(provider); }
49
+ static std::string captureFrame() {
50
+ auto *provider = frameProvider().load();
51
+ return provider ? provider->takeFrameDataUrl() : std::string();
52
+ }
53
+
39
54
  static int width();
40
55
  static int height();
41
56
  static int orientation();
@@ -102,6 +117,12 @@ public:
102
117
  static void setWhiteBalance(const std::string &mode, double temperatureK, double tint);
103
118
  static void setFocus(const std::string &mode, double pointX, double pointY);
104
119
  static void setTorch(const std::string &mode, double level);
120
+
121
+ private:
122
+ static std::atomic<CameraFrameProvider *> &frameProvider() {
123
+ static std::atomic<CameraFrameProvider *> provider{nullptr};
124
+ return provider;
125
+ }
105
126
  };
106
127
 
107
128
  } // namespace gea::platform::camera
@@ -0,0 +1,71 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+
4
+ #include <cstddef>
5
+ #include <cstdint>
6
+ #include <cstring>
7
+
8
+ namespace gea::framework::network {
9
+
10
+ // A bounded DNS responder for AP captive portals. Rebuild the question name
11
+ // to avoid retaining compression pointers into discarded additional records.
12
+ inline std::size_t captiveDnsReply(const std::uint8_t *query,
13
+ std::size_t length,
14
+ const std::uint8_t address[4],
15
+ std::uint8_t *reply, std::size_t capacity) {
16
+ if (length < 12 || length > 512 || capacity < 12 || (query[2] & 0xf8) ||
17
+ query[4] != 0 || query[5] != 1)
18
+ return 0;
19
+ std::memcpy(reply, query, 12);
20
+ reply[2] = 0x80 | (query[2] & 1);
21
+ reply[3] = 0x80;
22
+ std::memset(reply + 6, 0, 6);
23
+ std::size_t source = 12, target = 12, questionEnd = 0, jumps = 0,
24
+ nameBytes = 0;
25
+ for (;;) {
26
+ if (source >= length)
27
+ return 0;
28
+ const auto count = query[source++];
29
+ if ((count & 0xc0) == 0xc0) {
30
+ if (source >= length || ++jumps > 8)
31
+ return 0;
32
+ const auto offset = ((count & 0x3f) << 8) | query[source++];
33
+ if (!questionEnd)
34
+ questionEnd = source;
35
+ if (offset >= length)
36
+ return 0;
37
+ source = offset;
38
+ continue;
39
+ }
40
+ if (count > 63 || source + count > length ||
41
+ target + count + 1 > capacity || (nameBytes += count + 1) > 255)
42
+ return 0;
43
+ reply[target++] = count;
44
+ if (!count)
45
+ break;
46
+ std::memcpy(reply + target, query + source, count);
47
+ target += count;
48
+ source += count;
49
+ }
50
+ if (!questionEnd)
51
+ questionEnd = source;
52
+ if (questionEnd + 4 > length || target + 4 > capacity)
53
+ return 0;
54
+ const auto type = (query[questionEnd] << 8) | query[questionEnd + 1];
55
+ const auto klass = (query[questionEnd + 2] << 8) | query[questionEnd + 3];
56
+ std::memcpy(reply + target, query + questionEnd, 4);
57
+ target += 4;
58
+ // AAAA and other record types receive an empty successful answer so clients
59
+ // can issue their IPv4 query; this portal only owns its AP IPv4 address.
60
+ if (klass != 1 || (type != 1 && type != 255))
61
+ return target;
62
+ if (target + 16 > capacity)
63
+ return 0;
64
+ constexpr std::uint8_t answer[] = {0xc0, 0x0c, 0, 1, 0, 1, 0, 0, 0, 30, 0, 4};
65
+ std::memcpy(reply + target, answer, sizeof(answer));
66
+ std::memcpy(reply + target + sizeof(answer), address, 4);
67
+ reply[7] = 1;
68
+ return target + 16;
69
+ }
70
+
71
+ } // namespace gea::framework::network
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "canvas.h"
4
4
  #include "display.h"
5
+ #include "image.h"
5
6
 
6
7
  #include <algorithm>
7
8
  #include <array>
@@ -96,6 +97,7 @@ struct HeapCapsFree {
96
97
  static constexpr int kInlineCircleEntryCapacity = 32;
97
98
 
98
99
  struct Command {
100
+ gea::framework::graphics::ImageReference imageReference;
99
101
  using CircleEntry = gea::framework::graphics::CircleEntry;
100
102
 
101
103
  gea::platform::display::DisplayPresentCommandType type =
@@ -148,6 +150,9 @@ struct Command {
148
150
 
149
151
  struct Frame {
150
152
  std::vector<Command> commands;
153
+ // Embedded canvas batches preserve bitmap drawing semantics: circle painter
154
+ // order and the established triangle edge rasterization remain observable.
155
+ bool bitmapSemantics = false;
151
156
 
152
157
  void clear()
153
158
  {
@@ -284,6 +289,13 @@ inline void addCommandBounds(const Command &command, Rect *rects, int *count, in
284
289
  addRect(rects, count, capacity, commandBounds(command, width, height), width, height);
285
290
  }
286
291
 
292
+ // An app-registered buffer (native surface, ImageData565, video frame) is
293
+ // rewritten in place, so equal pointers say nothing about equal pixels.
294
+ inline bool imagePixelsStable(const Command &command)
295
+ {
296
+ return !gea::framework::graphics::ImageStore::instance().pixelsMayChange(command.pixels);
297
+ }
298
+
287
299
  inline bool commandsEqual(const Command &a, const Command &b)
288
300
  {
289
301
  if (a.type != b.type || a.alpha != b.alpha) return false;
@@ -300,36 +312,51 @@ inline bool commandsEqual(const Command &a, const Command &b)
300
312
  case Type::FillCircleRgb565:
301
313
  case Type::StrokeCircleRgb565:
302
314
  return a.x == b.x && a.y == b.y && a.radius == b.radius && a.color == b.color;
303
- case Type::FillCirclesRgb565: {
304
- if (a.radius != b.radius || a.circlesCount != b.circlesCount) return false;
305
- if (a.circlesCount == 0) return true;
315
+ case Type::FillCirclesRgb565:
316
+ {
317
+ if (a.radius != b.radius || a.circlesCount != b.circlesCount)
318
+ return false;
319
+ if (a.circlesCount == 0)
320
+ return true;
306
321
  const auto *ap = a.circleEntries();
307
322
  const auto *bp = b.circleEntries();
308
- if (!ap || !bp) return ap == bp;
309
- return std::memcmp(ap, bp,
310
- static_cast<std::size_t>(a.circlesCount) *
311
- sizeof(gea::framework::graphics::CircleEntry)) == 0;
323
+ if (!ap || !bp)
324
+ return ap == bp;
325
+ for (int i = 0; i < a.circlesCount; ++i)
326
+ if (ap[i].x != bp[i].x || ap[i].y != bp[i].y || ap[i].color != bp[i].color)
327
+ return false;
328
+ return true;
312
329
  }
313
- case Type::FillTrianglesRgb565: {
314
- if (a.trianglesCount != b.trianglesCount) return false;
315
- if (a.trianglesCount == 0) return true;
330
+ case Type::FillTrianglesRgb565:
331
+ {
332
+ if (a.trianglesCount != b.trianglesCount)
333
+ return false;
334
+ if (a.trianglesCount == 0)
335
+ return true;
316
336
  const auto *ap = a.trianglesBuffer.get();
317
337
  const auto *bp = b.trianglesBuffer.get();
318
- if (!ap || !bp) return ap == bp;
319
- return std::memcmp(ap, bp,
320
- static_cast<std::size_t>(a.trianglesCount) *
321
- sizeof(gea::framework::graphics::TriangleEntry)) == 0;
338
+ if (!ap || !bp)
339
+ return ap == bp;
340
+ for (int i = 0; i < a.trianglesCount; ++i)
341
+ {
342
+ const auto &x = ap[i];
343
+ const auto &y = bp[i];
344
+ if (x.x0 != y.x0 || x.y0 != y.y0 || x.x1 != y.x1 || x.y1 != y.y1 || x.x2 != y.x2 ||
345
+ x.y2 != y.y2 || x.color != y.color || x.rowY0 != y.rowY0 || x.rowY1 != y.rowY1)
346
+ return false;
347
+ }
348
+ return true;
322
349
  }
323
350
  case Type::DrawImage:
324
- return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
325
- a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight && a.x == b.x && a.y == b.y;
351
+ return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels && imagePixelsStable(a) &&
352
+ a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight && a.x == b.x && a.y == b.y;
326
353
  case Type::DrawImageScaled:
327
354
  case Type::DrawImageRotated90CW:
328
- return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
355
+ return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels && imagePixelsStable(a) &&
329
356
  a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight &&
330
357
  a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h;
331
358
  case Type::DrawImageTiledX:
332
- return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels &&
359
+ return a.pixels == b.pixels && a.alphaPixels == b.alphaPixels && imagePixelsStable(a) &&
333
360
  a.srcWidth == b.srcWidth && a.srcHeight == b.srcHeight &&
334
361
  a.x == b.x && a.y == b.y && a.w == b.w;
335
362
  case Type::FillText:
@@ -339,8 +366,9 @@ inline bool commandsEqual(const Command &a, const Command &b)
339
366
  return false;
340
367
  }
341
368
 
342
- inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *commands, int commandCount, Frame &frame)
369
+ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *commands, int commandCount, Frame &frame, bool bitmapSemantics = false)
343
370
  {
371
+ frame.bitmapSemantics = bitmapSemantics;
344
372
  frame.clear();
345
373
  if (!commands || commandCount <= 0) return false;
346
374
  frame.commands.reserve(static_cast<std::size_t>(commandCount));
@@ -427,7 +455,7 @@ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *co
427
455
  }
428
456
  bool sortedByBucket = false;
429
457
  constexpr int kCircleBucketSortMaxYRange = 2048;
430
- if (count <= 65535) {
458
+ if (!bitmapSemantics && count <= 65535) {
431
459
  int minY = static_cast<int>(src.ys[0]);
432
460
  int maxY = minY;
433
461
  for (int i = 1; i < count; i++) {
@@ -458,8 +486,9 @@ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *co
458
486
  for (int i = 0; i < count; i++) {
459
487
  buffer[i] = CircleEntry{src.ys[i], src.xs[i], src.colors[i]};
460
488
  }
461
- std::sort(buffer, buffer + count,
462
- [](const CircleEntry &a, const CircleEntry &b) { return a.y < b.y; });
489
+ if (!bitmapSemantics)
490
+ std::sort(buffer, buffer + count,
491
+ [](const CircleEntry &a, const CircleEntry &b) { return a.y < b.y; });
463
492
  }
464
493
  if (!out.circlesInline) out.circlesBuffer.reset(buffer);
465
494
  }
@@ -563,6 +592,8 @@ inline bool extractFrame(const gea::platform::display::DisplayPresentCommand *co
563
592
  out.alpha = command.fillText.alpha;
564
593
  break;
565
594
  }
595
+ out.imageReference = gea::framework::graphics::ImageReference(
596
+ gea::framework::graphics::ImageStore::instance().idForPixels(out.pixels));
566
597
  frame.commands.push_back(std::move(out));
567
598
  }
568
599
  return true;
@@ -574,8 +605,17 @@ inline bool frameHasOpaqueBase(const Frame &frame, int width, int height)
574
605
  const Command &first = frame.commands.front();
575
606
  using Type = gea::platform::display::DisplayPresentCommandType;
576
607
  if (first.type == Type::Clear) return true;
577
- if (first.type != Type::FillRectRgb565 || first.alpha != 255) return false;
578
- const Rect bounds = rectFromBox(first.x, first.y, first.w, first.h);
608
+ if (first.alpha != 255) return false;
609
+ Rect bounds;
610
+ if (first.type == Type::FillRectRgb565) {
611
+ bounds = rectFromBox(first.x, first.y, first.w, first.h);
612
+ } else if (first.type == Type::DrawImage && first.alphaPixels == nullptr) {
613
+ // A full-panel opaque image (putImageData of a whole-screen buffer)
614
+ // paints every pixel, the same as a full-panel fill.
615
+ bounds = rectFromBox(first.x, first.y, first.srcWidth, first.srcHeight);
616
+ } else {
617
+ return false;
618
+ }
579
619
  return bounds.x0 <= 0 && bounds.y0 <= 0 && bounds.x1 >= width - 1 && bounds.y1 >= height - 1;
580
620
  }
581
621
 
@@ -679,7 +719,8 @@ inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
679
719
  int row,
680
720
  int chunkRows,
681
721
  int displayHeight,
682
- const Command &command)
722
+ const Command &command,
723
+ bool bitmapSemantics = false)
683
724
  {
684
725
  if (!buffer) return;
685
726
  using Type = gea::platform::display::DisplayPresentCommandType;
@@ -748,6 +789,13 @@ inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
748
789
  canvas.strokeCircle(command.x - ox, command.y - oy, command.radius, command.color);
749
790
  break;
750
791
  case Type::FillCirclesRgb565: {
792
+ if (bitmapSemantics) {
793
+ const auto *circles = command.circleEntries();
794
+ for (int i = 0; circles && i < command.circlesCount; ++i)
795
+ canvas.fillCircle(int(circles[i].x) - ox, int(circles[i].y) - oy,
796
+ command.radius, circles[i].color);
797
+ break;
798
+ }
751
799
  // Hand the whole sorted batch to Canvas's optimized batch entrypoint.
752
800
  // It inlines fillCircleNoDirty's hot path and uses [chunkY0, chunkY1]
753
801
  // as a world-Y band to skip leading + break trailing in one loop.
@@ -760,21 +808,28 @@ inline void rasterCommandRows(gea::framework::graphics::Canvas &canvas,
760
808
  break;
761
809
  }
762
810
  case Type::FillTrianglesRgb565: {
763
- // Depth-ordered batch (farthest-first). OPAQUE batches take the span-
764
- // occlusion path (front-to-back, each pixel written once); it band-rejects
765
- // per chunk internally. TRANSLUCENT must stay painter's back-to-front.
811
+ // Full-display opaque 3D batches retain their front-to-back occlusion
812
+ // optimization. Bitmap canvas batches and translucent geometry replay
813
+ // in producer order, rejecting triangles outside this strip first.
766
814
  const gea::framework::graphics::TriangleEntry *tris = command.trianglesBuffer.get();
767
815
  const int count = command.trianglesCount;
768
816
  if (!tris) break;
769
- if (command.alpha == 255) {
817
+ if (command.alpha == 255 && !bitmapSemantics) {
770
818
  canvas.fillTrianglesOpaqueOccluded(tris, count, ox, oy);
771
819
  } else {
820
+ // Retained canvas batches preserve producer order. Sparse numeric
821
+ // geometry needs no coverage bitset; opaque spans avoid per-pixel
822
+ // comparisons while sharing the public triangle edge definition.
772
823
  const int chunkY1 = row + chunkRows - 1;
773
824
  for (int i = 0; i < count; i++) {
774
825
  const auto &t = tris[i];
775
826
  if (t.rowY1 < row || t.rowY0 > chunkY1) continue;
776
- canvas.fillTriangle(t.x0 - ox, t.y0 - oy, t.x1 - ox, t.y1 - oy,
777
- t.x2 - ox, t.y2 - oy, t.color);
827
+ if (command.alpha == 255)
828
+ canvas.fillTriangleOpaque(t.x0 - ox, t.y0 - oy, t.x1 - ox, t.y1 - oy,
829
+ t.x2 - ox, t.y2 - oy, t.color);
830
+ else
831
+ canvas.fillTriangle(t.x0 - ox, t.y0 - oy, t.x1 - ox, t.y1 - oy,
832
+ t.x2 - ox, t.y2 - oy, t.color);
778
833
  }
779
834
  }
780
835
  break;
@@ -834,7 +889,7 @@ inline void rasterFrameRows(gea::framework::graphics::pixel::native_t *buffer,
834
889
  gea::framework::graphics::Canvas canvas;
835
890
  canvas.bindPixels(buffer, regionWidth, chunkRows);
836
891
  for (const Command &command : frame.commands) {
837
- rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command);
892
+ rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command, frame.bitmapSemantics);
838
893
  }
839
894
  }
840
895
 
@@ -862,7 +917,7 @@ inline void rasterFrameRowsStrided(gea::framework::graphics::pixel::native_t *bu
862
917
  }
863
918
  continue;
864
919
  }
865
- rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command);
920
+ rasterCommandRows(canvas, buffer, regionX0, regionWidth, row, chunkRows, displayHeight, command, frame.bitmapSemantics);
866
921
  }
867
922
  }
868
923
 
package/include/events.h CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  #include <cstdint>
6
6
  #include <functional>
7
+ #include <memory>
7
8
 
8
9
  namespace gea::framework::events {
9
10
 
@@ -15,7 +16,8 @@ enum class PointerEventType {
15
16
  Input,
16
17
  KeyDown,
17
18
  Rotary,
18
- Scroll
19
+ Scroll,
20
+ KeyUp
19
21
  };
20
22
 
21
23
  enum class EventPhase {
@@ -51,6 +53,32 @@ struct TouchPoint {
51
53
  int pageY = 0;
52
54
  };
53
55
 
56
+ // Native callback lowering may copy the event carrier. Its cancellation flags
57
+ // still belong to the same dispatched event, including copies retained later.
58
+ // Allocate shared storage only when a carrier is actually copied.
59
+ class EventControlFlag {
60
+ public:
61
+ EventControlFlag(bool value = false) : initial_(value) {}
62
+ EventControlFlag(const EventControlFlag &other) : shared_(other.share()) {}
63
+ EventControlFlag &operator=(const EventControlFlag &other) {
64
+ if (this != &other) shared_ = other.share();
65
+ return *this;
66
+ }
67
+ EventControlFlag &operator=(bool value) {
68
+ if (shared_) *shared_ = value;
69
+ else initial_ = value;
70
+ return *this;
71
+ }
72
+ operator bool() const { return shared_ ? *shared_ : initial_; }
73
+ private:
74
+ std::shared_ptr<bool> share() const {
75
+ if (!shared_) shared_ = std::make_shared<bool>(initial_);
76
+ return shared_;
77
+ }
78
+ bool initial_ = false;
79
+ mutable std::shared_ptr<bool> shared_;
80
+ };
81
+
54
82
  struct PointerEvent {
55
83
  PointerEventType type;
56
84
  EventTarget target{};
@@ -73,8 +101,8 @@ struct PointerEvent {
73
101
  bool primary = true;
74
102
  bool bubbles = true;
75
103
  bool cancelable = true;
76
- bool defaultPrevented = false;
77
- bool propagationStopped = false;
104
+ EventControlFlag defaultPrevented;
105
+ EventControlFlag propagationStopped;
78
106
  EventPhase eventPhase = EventPhase::None;
79
107
  TouchPoint touches[1]{};
80
108
  TouchPoint targetTouches[1]{};
@@ -11,3 +11,5 @@
11
11
  #include "gea/embedded-host.h"
12
12
  #include "ui/document.h"
13
13
  #include "ui/signal.h"
14
+ // Debug builds: attributes JSX listeners to nodes for DevTools.
15
+ #include "ui/debugger_listeners.h"
@@ -13,6 +13,7 @@ struct Glyph {
13
13
  int advance = 0;
14
14
  int bearingX = 0;
15
15
  int bearingY = 0;
16
+ int advance16 = -1;
16
17
  };
17
18
 
18
19
  class Font {
@@ -25,6 +26,12 @@ public:
25
26
  virtual int ascender() const = 0;
26
27
  virtual bool glyph(int codepoint, Glyph *out) const = 0;
27
28
  virtual std::uint8_t coverage(const Glyph &glyph, int row, int col) const = 0;
29
+ virtual int advance(int codepoint, int nextCodepoint = 0) const
30
+ {
31
+ (void)nextCodepoint;
32
+ Glyph value{};
33
+ return glyph(codepoint, &value) ? value.advance : sizePx() / 2;
34
+ }
28
35
  };
29
36
 
30
37
  class BitmapFont : public Font {
@@ -63,6 +70,13 @@ private:
63
70
  static const std::uint8_t glyphs[kGlyphCount][kHeight];
64
71
  };
65
72
 
73
+ struct FontKerningPair
74
+ {
75
+ int left = 0;
76
+ int right = 0;
77
+ int adjustment16 = 0;
78
+ };
79
+
66
80
  struct RasterizedFontData {
67
81
  int id = 0;
68
82
  int sizePx = 0;
@@ -80,6 +94,10 @@ struct RasterizedFontData {
80
94
  // byte-aligned (stride = (atlasWidth + 3) / 4) and the leftmost pixel of a
81
95
  // byte sits in the most significant bit pair.
82
96
  int atlasBits = 8;
97
+ int kerningCount = 0;
98
+ const FontKerningPair *kerning = nullptr;
99
+ bool strictGlyphLookup = false;
100
+ int fallbackCodepoint = -1;
83
101
  };
84
102
 
85
103
  class RasterizedFont final : public Font {
@@ -93,6 +111,7 @@ public:
93
111
  int ascender() const final;
94
112
  bool glyph(int codepoint, Glyph *out) const final;
95
113
  std::uint8_t coverage(const Glyph &glyph, int row, int col) const final;
114
+ int advance(int codepoint, int nextCodepoint = 0) const final;
96
115
  const RasterizedFontData *data() const { return data_; }
97
116
 
98
117
  private: