@geastack/apple 0.2.8
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/LICENSE +202 -0
- package/README.md +54 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +3076 -0
- package/generated/AVFoundation.d.ts +112 -0
- package/generated/ActivityKit.d.ts +7 -0
- package/generated/AppKit.d.ts +268 -0
- package/generated/CoreGraphics.d.ts +18 -0
- package/generated/CoreLocation.d.ts +28 -0
- package/generated/CoreMedia.d.ts +8 -0
- package/generated/Dispatch.d.ts +2 -0
- package/generated/Foundation.d.ts +13 -0
- package/generated/MapKit.d.ts +28 -0
- package/generated/Metal.d.ts +91 -0
- package/generated/MetalKit.d.ts +22 -0
- package/generated/Photos.d.ts +5 -0
- package/generated/QuartzCore.d.ts +12 -0
- package/generated/SwiftData.d.ts +11 -0
- package/generated/UIKit.d.ts +187 -0
- package/package.json +123 -0
- package/runtime/AVFoundation.js +136 -0
- package/runtime/ActivityKit.js +18 -0
- package/runtime/AppKit.js +313 -0
- package/runtime/CoreGraphics.js +15 -0
- package/runtime/CoreLocation.js +30 -0
- package/runtime/CoreMedia.js +7 -0
- package/runtime/Dispatch.js +11 -0
- package/runtime/Foundation.js +21 -0
- package/runtime/MapKit.js +22 -0
- package/runtime/Metal.js +126 -0
- package/runtime/MetalKit.js +15 -0
- package/runtime/Photos.js +9 -0
- package/runtime/QuartzCore.js +9 -0
- package/runtime/SwiftData.js +29 -0
- package/runtime/UIKit.js +242 -0
- package/runtime/empty.js +1 -0
- package/targets/ios/Info.plist.in +25 -0
- package/targets/ios/README.md +46 -0
- package/targets/ios/build-ios.sh +395 -0
- package/targets/ios/detect-development-team.mjs +109 -0
- package/targets/ios/generate-resident-entry.mjs +67 -0
- package/targets/ios/generate-resident-registry.mjs +162 -0
- package/targets/ios/generate-xcode-project.mjs +520 -0
- package/targets/ios/main/canvas_view.h +7 -0
- package/targets/ios/main/canvas_view.mm +94 -0
- package/targets/ios/main/font_registry.h +17 -0
- package/targets/ios/main/font_registry.mm +129 -0
- package/targets/ios/main/image_bridge.h +13 -0
- package/targets/ios/main/image_bridge.mm +53 -0
- package/targets/ios/main/ios_apps.c +28 -0
- package/targets/ios/main/ios_camera.mm +446 -0
- package/targets/ios/main/ios_display.mm +344 -0
- package/targets/ios/main/ios_main.mm +811 -0
- package/targets/ios/main/ios_memory.cpp +55 -0
- package/targets/ios/main/ios_renderer.h +20 -0
- package/targets/ios/main/ios_renderer.mm +27 -0
- package/targets/ios/main/ios_root_background.cpp +20 -0
- package/targets/ios/main/ios_root_background.h +11 -0
- package/targets/ios/main/ios_sensors.mm +244 -0
- package/targets/ios/main/ios_timers.mm +53 -0
- package/targets/ios/main/renderer/ios_renderer_internal.h +65 -0
- package/targets/ios/main/renderer/ios_renderer_support.mm +111 -0
- package/targets/ios/main/renderer/native_button.mm +179 -0
- package/targets/ios/main/renderer/native_label.mm +161 -0
- package/targets/ios/main/renderer/native_scroll_container.mm +279 -0
- package/targets/ios/main/renderer/view_reconciler.mm +274 -0
- package/targets/macos/Info.plist.in +26 -0
- package/targets/macos/README.md +131 -0
- package/targets/macos/build-child-lifecycle.sh +119 -0
- package/targets/macos/build-macos.sh +2156 -0
- package/targets/macos/check-module-graph-freshness.mjs +82 -0
- package/targets/macos/compiler-input-fingerprint.mjs +32 -0
- package/targets/macos/finalize-apple-native-output.mjs +77 -0
- package/targets/macos/generate-resident-entry.mjs +80 -0
- package/targets/macos/generate-resident-registry.mjs +141 -0
- package/targets/macos/heavy-build-lock.sh +176 -0
- package/targets/macos/main/canvas_view.h +9 -0
- package/targets/macos/main/canvas_view.mm +176 -0
- package/targets/macos/main/color_convert.h +18 -0
- package/targets/macos/main/color_convert.mm +39 -0
- package/targets/macos/main/font_registry.h +35 -0
- package/targets/macos/main/font_registry.mm +350 -0
- package/targets/macos/main/image_bridge.h +16 -0
- package/targets/macos/main/image_bridge.mm +52 -0
- package/targets/macos/main/macos_app_platform.mm +95 -0
- package/targets/macos/main/macos_apps.c +31 -0
- package/targets/macos/main/macos_device_control.mm +56 -0
- package/targets/macos/main/macos_display.mm +484 -0
- package/targets/macos/main/macos_host_device_control.mm +95 -0
- package/targets/macos/main/macos_main.mm +839 -0
- package/targets/macos/main/macos_memory.cpp +31 -0
- package/targets/macos/main/macos_native_shell.h +31 -0
- package/targets/macos/main/macos_native_shell.mm +396 -0
- package/targets/macos/main/macos_network.mm +253 -0
- package/targets/macos/main/macos_renderer.h +39 -0
- package/targets/macos/main/macos_renderer.mm +1969 -0
- package/targets/macos/main/macos_sensors.cpp +148 -0
- package/targets/macos/main/macos_smoke_app.cpp +65 -0
- package/targets/macos/main/macos_timers.mm +52 -0
- package/targets/macos/main/press_bridge.h +21 -0
- package/targets/macos/main/press_bridge.mm +72 -0
- package/targets/macos/main/thermalright_hid_display.h +14 -0
- package/targets/macos/main/thermalright_hid_display.mm +995 -0
- package/targets/macos/native-artifact-provenance.mjs +421 -0
- package/targets/macos/order-compile-edges.mjs +64 -0
- package/targets/macos/preflight-hard-muted-artifact.mjs +93 -0
- package/targets/macos/prune-generated-modules.mjs +147 -0
- package/targets/macos/sync-resources.mjs +153 -0
- package/targets/shared/analyze-audio-capture.mjs +131 -0
- package/targets/shared/apple_audio.mm +1194 -0
- package/targets/shared/apple_audio_selftest.mm +602 -0
- package/targets/shared/apple_audio_testing.h +34 -0
- package/targets/shared/build-audio-selftest.sh +50 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#import "canvas_view.h"
|
|
2
|
+
|
|
3
|
+
#include "ui/tree_internal.h"
|
|
4
|
+
#include "canvas.h"
|
|
5
|
+
#include "events.h"
|
|
6
|
+
#include "host/display_orientation.h"
|
|
7
|
+
#include "pixel.h"
|
|
8
|
+
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <cmath>
|
|
11
|
+
|
|
12
|
+
extern "C" void gea_macos_touch_set_state(int touching, int x, int y);
|
|
13
|
+
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
// Single shared scratch buffer for RGB565→RGBA8888 expansion. Sized lazily to
|
|
17
|
+
// the largest canvas seen. -drawRect: is main-thread-only so no locking.
|
|
18
|
+
std::uint8_t *g_scratch = nullptr;
|
|
19
|
+
size_t g_scratchBytes = 0;
|
|
20
|
+
|
|
21
|
+
std::uint8_t *ensureScratch(size_t bytes)
|
|
22
|
+
{
|
|
23
|
+
if (g_scratchBytes < bytes) {
|
|
24
|
+
void *grown = std::realloc(g_scratch, bytes);
|
|
25
|
+
if (!grown) return nullptr;
|
|
26
|
+
g_scratch = static_cast<std::uint8_t *>(grown);
|
|
27
|
+
g_scratchBytes = bytes;
|
|
28
|
+
}
|
|
29
|
+
return g_scratch;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
} // namespace
|
|
33
|
+
|
|
34
|
+
@implementation GeaCanvasView
|
|
35
|
+
|
|
36
|
+
- (instancetype)initWithFrame:(NSRect)frame
|
|
37
|
+
{
|
|
38
|
+
self = [super initWithFrame:frame];
|
|
39
|
+
if (self) {
|
|
40
|
+
_nodeId = -1;
|
|
41
|
+
}
|
|
42
|
+
return self;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Stay UNFLIPPED. The canvas pixel buffer is stored top-down (row 0 = top),
|
|
46
|
+
// which is what CGContextDrawImage expects when the destination context uses
|
|
47
|
+
// default Quartz (bottom-up) coords. Setting isFlipped:YES would make AppKit
|
|
48
|
+
// apply an implicit Y-inversion and the image would render upside-down. The
|
|
49
|
+
// renderer that positions this view already converts CSS top-left coords to
|
|
50
|
+
// AppKit bottom-left when computing our frame, so unflipped is correct.
|
|
51
|
+
- (BOOL)isFlipped { return NO; }
|
|
52
|
+
|
|
53
|
+
// Mouse → framework touch events, mirroring the iOS renderer's dispatchTouch
|
|
54
|
+
// flow. Canvas apps (maps, games) read gesture coordinates off onPointerDown/
|
|
55
|
+
// Move/Up — the AppKit click recognizer only fires coordinate-less presses, so
|
|
56
|
+
// without this a drag never pans and every tap lands at 0,0. The recognizer's
|
|
57
|
+
// delegate declines canvas views, so events arrive here exactly once.
|
|
58
|
+
- (BOOL)acceptsFirstMouse:(NSEvent *)event
|
|
59
|
+
{
|
|
60
|
+
(void)event;
|
|
61
|
+
return YES;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
- (void)dispatchMousePhase:(gea::framework::events::TouchPhase)phase
|
|
65
|
+
touching:(bool)touching
|
|
66
|
+
event:(NSEvent *)event
|
|
67
|
+
{
|
|
68
|
+
if (self.nodeId < 0) return;
|
|
69
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
70
|
+
if (self.nodeId >= tree.nodeCount()) return;
|
|
71
|
+
const auto *canvas = tree.canvas(self.nodeId);
|
|
72
|
+
const NSRect bounds = self.bounds;
|
|
73
|
+
if (!canvas || bounds.size.width <= 0 || bounds.size.height <= 0) return;
|
|
74
|
+
|
|
75
|
+
// View point (bottom-up) → top-down surface coords, scaled to the canvas
|
|
76
|
+
// pixel grid (drawRect stretches the canvas into the view bounds, so input
|
|
77
|
+
// must apply the inverse of that stretch to stay aligned).
|
|
78
|
+
const NSPoint p = [self convertPoint:event.locationInWindow fromView:nil];
|
|
79
|
+
const double scaleX = canvas->width() / bounds.size.width;
|
|
80
|
+
const double scaleY = canvas->height() / bounds.size.height;
|
|
81
|
+
const int lx = std::clamp((int)std::lround(p.x * scaleX), 0, canvas->width() - 1);
|
|
82
|
+
const int ly = std::clamp((int)std::lround((bounds.size.height - 1 - p.y) * scaleY), 0,
|
|
83
|
+
canvas->height() - 1);
|
|
84
|
+
|
|
85
|
+
// The latest-move cache holds PANEL-NATIVE coordinates (the hardware touch
|
|
86
|
+
// controller contract): the dispatcher re-reads Move coords from it through
|
|
87
|
+
// transformTouchToLogical, so store the inverse-transformed point.
|
|
88
|
+
namespace od = gea::framework::display::detail;
|
|
89
|
+
using gea::framework::display::DisplayOrientation;
|
|
90
|
+
int px = lx;
|
|
91
|
+
int py = ly;
|
|
92
|
+
switch (od::DisplayOrientationState::orientation()) {
|
|
93
|
+
case DisplayOrientation::LandscapePrimary:
|
|
94
|
+
px = ly;
|
|
95
|
+
py = od::DisplayOrientationState::nativeHeight() - 1 - lx;
|
|
96
|
+
break;
|
|
97
|
+
case DisplayOrientation::LandscapeSecondary:
|
|
98
|
+
px = od::DisplayOrientationState::nativeWidth() - 1 - ly;
|
|
99
|
+
py = lx;
|
|
100
|
+
break;
|
|
101
|
+
case DisplayOrientation::PortraitSecondary:
|
|
102
|
+
px = od::DisplayOrientationState::nativeWidth() - 1 - lx;
|
|
103
|
+
py = od::DisplayOrientationState::nativeHeight() - 1 - ly;
|
|
104
|
+
break;
|
|
105
|
+
case DisplayOrientation::PortraitPrimary:
|
|
106
|
+
default:
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
gea_macos_touch_set_state(touching ? 1 : 0, px, py);
|
|
110
|
+
|
|
111
|
+
gea::framework::events::Event ev{};
|
|
112
|
+
ev.type = gea::framework::events::EventType::Touch;
|
|
113
|
+
ev.touchPhase = phase;
|
|
114
|
+
ev.touching = touching;
|
|
115
|
+
ev.x = lx;
|
|
116
|
+
ev.y = ly;
|
|
117
|
+
ev.pointerId = 0;
|
|
118
|
+
gea::framework::events::TouchRuntime::dispatchEvent(ev);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
- (void)mouseDown:(NSEvent *)event
|
|
122
|
+
{
|
|
123
|
+
[self dispatchMousePhase:gea::framework::events::TouchPhase::Down touching:true event:event];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
- (void)mouseDragged:(NSEvent *)event
|
|
127
|
+
{
|
|
128
|
+
[self dispatchMousePhase:gea::framework::events::TouchPhase::Move touching:true event:event];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
- (void)mouseUp:(NSEvent *)event
|
|
132
|
+
{
|
|
133
|
+
[self dispatchMousePhase:gea::framework::events::TouchPhase::Up touching:false event:event];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
- (void)drawRect:(NSRect)dirtyRect
|
|
137
|
+
{
|
|
138
|
+
(void)dirtyRect;
|
|
139
|
+
if (self.nodeId < 0) return;
|
|
140
|
+
auto &tree = gea::embedded::ui::Tree::instance();
|
|
141
|
+
if (self.nodeId >= tree.nodeCount()) return;
|
|
142
|
+
const auto *canvas = tree.canvas(self.nodeId);
|
|
143
|
+
if (!canvas) return;
|
|
144
|
+
const std::uint16_t *src = canvas->pixels();
|
|
145
|
+
const int w = canvas->width();
|
|
146
|
+
const int h = canvas->height();
|
|
147
|
+
if (!src || w <= 0 || h <= 0) return;
|
|
148
|
+
|
|
149
|
+
const size_t bytes = (size_t)w * (size_t)h * 4;
|
|
150
|
+
std::uint8_t *dst = ensureScratch(bytes);
|
|
151
|
+
if (!dst) return;
|
|
152
|
+
for (int i = 0; i < w * h; i++) {
|
|
153
|
+
int r, g, b;
|
|
154
|
+
gea::framework::graphics::pixel::unpackRgb565(src[i], &r, &g, &b);
|
|
155
|
+
dst[i * 4 + 0] = static_cast<std::uint8_t>((r * 255 + 15) / 31);
|
|
156
|
+
dst[i * 4 + 1] = static_cast<std::uint8_t>((g * 255 + 31) / 63);
|
|
157
|
+
dst[i * 4 + 2] = static_cast<std::uint8_t>((b * 255 + 15) / 31);
|
|
158
|
+
dst[i * 4 + 3] = 0xFF;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
|
162
|
+
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, dst, bytes, NULL);
|
|
163
|
+
const uint32_t bitmapInfo = (uint32_t)kCGImageAlphaNoneSkipLast | (uint32_t)kCGBitmapByteOrder32Big;
|
|
164
|
+
CGImageRef img = CGImageCreate((size_t)w, (size_t)h, 8, 32, (size_t)w * 4,
|
|
165
|
+
cs, bitmapInfo,
|
|
166
|
+
provider, NULL, false, kCGRenderingIntentDefault);
|
|
167
|
+
|
|
168
|
+
CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext];
|
|
169
|
+
CGContextDrawImage(ctx, NSRectToCGRect(self.bounds), img);
|
|
170
|
+
|
|
171
|
+
CGImageRelease(img);
|
|
172
|
+
CGDataProviderRelease(provider);
|
|
173
|
+
CGColorSpaceRelease(cs);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
|
|
5
|
+
#ifdef __OBJC__
|
|
6
|
+
@class NSColor;
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
namespace gea::macos {
|
|
10
|
+
|
|
11
|
+
#ifdef __OBJC__
|
|
12
|
+
NSColor *rgb565ToNSColor(std::uint16_t rgb565);
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
// Caller-owns the returned CGColorRef; release with CGColorRelease.
|
|
16
|
+
struct CGColor *rgb565ToCGColor(std::uint16_t rgb565);
|
|
17
|
+
|
|
18
|
+
} // namespace gea::macos
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#import <AppKit/AppKit.h>
|
|
2
|
+
|
|
3
|
+
#include "color_convert.h"
|
|
4
|
+
#include "pixel.h"
|
|
5
|
+
|
|
6
|
+
namespace gea::macos {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
void unpack(std::uint16_t rgb565, CGFloat *r, CGFloat *g, CGFloat *b)
|
|
11
|
+
{
|
|
12
|
+
int ri, gi, bi;
|
|
13
|
+
gea::framework::graphics::pixel::unpackRgb565(rgb565, &ri, &gi, &bi);
|
|
14
|
+
*r = static_cast<CGFloat>(ri) / 31.0;
|
|
15
|
+
*g = static_cast<CGFloat>(gi) / 63.0;
|
|
16
|
+
*b = static_cast<CGFloat>(bi) / 31.0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
} // namespace
|
|
20
|
+
|
|
21
|
+
NSColor *rgb565ToNSColor(std::uint16_t rgb565)
|
|
22
|
+
{
|
|
23
|
+
CGFloat r, g, b;
|
|
24
|
+
unpack(rgb565, &r, &g, &b);
|
|
25
|
+
return [NSColor colorWithSRGBRed:r green:g blue:b alpha:1.0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
CGColor *rgb565ToCGColor(std::uint16_t rgb565)
|
|
29
|
+
{
|
|
30
|
+
CGFloat r, g, b;
|
|
31
|
+
unpack(rgb565, &r, &g, &b);
|
|
32
|
+
CGFloat comps[4] = {r, g, b, 1.0};
|
|
33
|
+
CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
|
34
|
+
CGColorRef color = CGColorCreate(cs, comps);
|
|
35
|
+
CGColorSpaceRelease(cs);
|
|
36
|
+
return color;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
} // namespace gea::macos
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
|
|
5
|
+
#ifdef __OBJC__
|
|
6
|
+
#import <AppKit/AppKit.h>
|
|
7
|
+
@class NSFont;
|
|
8
|
+
@class NSString;
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
namespace gea::macos {
|
|
12
|
+
|
|
13
|
+
#ifdef __OBJC__
|
|
14
|
+
// Returns an NSFont for the given gea font id at the requested pixel size.
|
|
15
|
+
// fontId comes from the framework's lookupFontFamily which we override below
|
|
16
|
+
// to assign stable ids the first time each family is seen. Unknown ids fall
|
|
17
|
+
// back to the system font.
|
|
18
|
+
NSFont *fontForId(int fontId, int sizePx);
|
|
19
|
+
|
|
20
|
+
// Walks `directoryPath` for .ttf / .otf files and registers each with
|
|
21
|
+
// CTFontManager in the process scope. Returns the count of newly registered
|
|
22
|
+
// fonts; already-registered files are silently skipped. Call once at startup
|
|
23
|
+
// after the app bundle is known.
|
|
24
|
+
int registerCustomTtfDirectory(NSString *directoryPath);
|
|
25
|
+
|
|
26
|
+
// Picks the right NSLineBreakMode for `text` rendered in `font` within
|
|
27
|
+
// `maxWidth` points. Returns word-wrap when every space-separated chunk fits
|
|
28
|
+
// on a line by itself, otherwise char-wrap so that a long unbreakable run
|
|
29
|
+
// (like "TYPOGRAPHY") gets split mid-glyph instead of overflowing and being
|
|
30
|
+
// clipped at the field edge. Shared between the host measurement hook and
|
|
31
|
+
// the NSTextField renderer so both compute identical geometry.
|
|
32
|
+
NSLineBreakMode lineBreakModeForText(NSString *text, NSFont *font, CGFloat maxWidth);
|
|
33
|
+
#endif
|
|
34
|
+
|
|
35
|
+
} // namespace gea::macos
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
#import <AppKit/AppKit.h>
|
|
2
|
+
#import <CoreText/CoreText.h>
|
|
3
|
+
|
|
4
|
+
#include "font_registry.h"
|
|
5
|
+
#include "graphics/font.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cctype>
|
|
9
|
+
#include <cmath>
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <mutex>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <unordered_map>
|
|
15
|
+
#include <utility>
|
|
16
|
+
#include <vector>
|
|
17
|
+
|
|
18
|
+
namespace gea::macos {
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
// Family name registry. Indexed by familyId; ids are handed out in the order
|
|
23
|
+
// the framework first asks for them. CSS family lookups all flow through
|
|
24
|
+
// generated::lookupFontFamily below.
|
|
25
|
+
//
|
|
26
|
+
// Construct-on-first-use, NOT namespace-scope globals: an app with a static
|
|
27
|
+
// `font-family` CSS rule (maps' device-fonts.css) registers it from a GLOBAL
|
|
28
|
+
// CONSTRUCTOR — the generated GeaPluginCppPreludeRegistration calls
|
|
29
|
+
// registerStaticFontFamilyRule → lookupFontFamily during dyld initializers,
|
|
30
|
+
// which may run BEFORE this translation unit's own initializers. With plain
|
|
31
|
+
// globals the unordered_map was still zeroed BSS at that point (max load
|
|
32
|
+
// factor 0 → __next_prime overflow_error → abort at launch), and locking the
|
|
33
|
+
// unconstructed mutex is UB. A function-local static is constructed on first
|
|
34
|
+
// use (thread-safe since C++11), so the registry is valid whenever reached.
|
|
35
|
+
struct FamilyRegistry {
|
|
36
|
+
std::mutex lock;
|
|
37
|
+
std::vector<std::string> names;
|
|
38
|
+
std::unordered_map<std::string, int> byName;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
FamilyRegistry &familyRegistry()
|
|
42
|
+
{
|
|
43
|
+
static FamilyRegistry registry;
|
|
44
|
+
return registry;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
int registerFamily(const std::string &name)
|
|
48
|
+
{
|
|
49
|
+
FamilyRegistry ®istry = familyRegistry();
|
|
50
|
+
std::scoped_lock guard(registry.lock);
|
|
51
|
+
auto it = registry.byName.find(name);
|
|
52
|
+
if (it != registry.byName.end()) return it->second;
|
|
53
|
+
const int id = static_cast<int>(registry.names.size());
|
|
54
|
+
registry.names.push_back(name);
|
|
55
|
+
registry.byName.emplace(name, id);
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
NSString *familyForId(int id)
|
|
60
|
+
{
|
|
61
|
+
FamilyRegistry ®istry = familyRegistry();
|
|
62
|
+
std::scoped_lock guard(registry.lock);
|
|
63
|
+
if (id < 0 || static_cast<size_t>(id) >= registry.names.size()) return nil;
|
|
64
|
+
return [NSString stringWithUTF8String:registry.names[id].c_str()];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Raw TTF/OTF bytes of the bundle's custom fonts, keyed by lowercased family
|
|
68
|
+
// name. The engine's runtime TTF rasterizer (GEA_EMBEDDED_TTF_RUNTIME_FONTS)
|
|
69
|
+
// bakes canvas glyph atlases from these via lookupRuntimeTtfFontForFamily —
|
|
70
|
+
// without it, ctx.fillText fell back to the builtin bitmap font and canvas
|
|
71
|
+
// labels (maps' place pins) rendered garbled. The byte buffers must live for
|
|
72
|
+
// the process lifetime: the rasterizer keeps stbtt_fontinfo pointers into
|
|
73
|
+
// them across atlas rebuilds. Construct-on-first-use for the same dyld-
|
|
74
|
+
// initializer-ordering reason as FamilyRegistry above.
|
|
75
|
+
struct TtfByteRegistry {
|
|
76
|
+
std::mutex lock;
|
|
77
|
+
std::unordered_map<std::string, std::vector<std::uint8_t>> byFamily;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
TtfByteRegistry &ttfByteRegistry()
|
|
81
|
+
{
|
|
82
|
+
static TtfByteRegistry registry;
|
|
83
|
+
return registry;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
std::string lowercased(const std::string &value)
|
|
87
|
+
{
|
|
88
|
+
std::string out = value;
|
|
89
|
+
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void registerTtfBytesForFamily(NSString *path)
|
|
94
|
+
{
|
|
95
|
+
NSData *data = [NSData dataWithContentsOfFile:path];
|
|
96
|
+
if (data.length == 0) return;
|
|
97
|
+
CTFontDescriptorRef desc = CTFontManagerCreateFontDescriptorFromData((__bridge CFDataRef)data);
|
|
98
|
+
if (!desc) return;
|
|
99
|
+
CFStringRef family = (CFStringRef)CTFontDescriptorCopyAttribute(desc, kCTFontFamilyNameAttribute);
|
|
100
|
+
CFRelease(desc);
|
|
101
|
+
if (!family) return;
|
|
102
|
+
char buf[256] = {0};
|
|
103
|
+
const bool ok = CFStringGetCString(family, buf, sizeof(buf), kCFStringEncodingUTF8);
|
|
104
|
+
CFRelease(family);
|
|
105
|
+
if (!ok || !buf[0]) return;
|
|
106
|
+
const auto *bytes = static_cast<const std::uint8_t *>(data.bytes);
|
|
107
|
+
TtfByteRegistry ®istry = ttfByteRegistry();
|
|
108
|
+
std::scoped_lock guard(registry.lock);
|
|
109
|
+
// First face wins (e.g. Inter-Regular before other weights of the family):
|
|
110
|
+
// the runtime rasterizer keys on the family alone.
|
|
111
|
+
registry.byFamily.emplace(lowercased(buf), std::vector<std::uint8_t>(bytes, bytes + data.length));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
} // namespace
|
|
115
|
+
|
|
116
|
+
// Non-anonymous accessor for the generated-hook namespace below: resolve a
|
|
117
|
+
// framework familyId to the bundled TTF bytes feeding the runtime rasterizer.
|
|
118
|
+
const std::uint8_t *runtimeTtfBytesForFamilyId(int familyId, unsigned long *length)
|
|
119
|
+
{
|
|
120
|
+
if (length) *length = 0;
|
|
121
|
+
NSString *family = familyForId(familyId);
|
|
122
|
+
if (!family || family.length == 0) return nullptr;
|
|
123
|
+
TtfByteRegistry ®istry = ttfByteRegistry();
|
|
124
|
+
std::scoped_lock guard(registry.lock);
|
|
125
|
+
const auto it = registry.byFamily.find(lowercased(family.UTF8String));
|
|
126
|
+
if (it == registry.byFamily.end()) return nullptr;
|
|
127
|
+
if (length) *length = it->second.size();
|
|
128
|
+
return it->second.data();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static NSFont *self_resolveFont(int fontId, CGFloat size);
|
|
132
|
+
|
|
133
|
+
NSFont *fontForId(int fontId, int sizePx)
|
|
134
|
+
{
|
|
135
|
+
CGFloat size = sizePx > 0 ? static_cast<CGFloat>(sizePx) : [NSFont systemFontSize];
|
|
136
|
+
|
|
137
|
+
// Cache resolved fonts by (fontId, size). `+[NSFont fontWithName:size:]` and
|
|
138
|
+
// the CTFontDescriptor fallback below both do a CoreText font-descriptor
|
|
139
|
+
// match, which is expensive — and this is called for EVERY text node on
|
|
140
|
+
// EVERY frame (renderer sync + layout measurement). Without this cache,
|
|
141
|
+
// CoreText font matching dominated the profile and pegged a CPU core. The
|
|
142
|
+
// set of (family, size) pairs an app uses is tiny, so the cache stays small.
|
|
143
|
+
static NSMutableDictionary<NSNumber *, NSFont *> *cache;
|
|
144
|
+
static dispatch_once_t once;
|
|
145
|
+
dispatch_once(&once, ^{ cache = [NSMutableDictionary dictionary]; });
|
|
146
|
+
NSNumber *key = @(static_cast<long long>(fontId) * 100000LL + llround(size));
|
|
147
|
+
@synchronized(cache) {
|
|
148
|
+
NSFont *hit = cache[key];
|
|
149
|
+
if (hit) return hit;
|
|
150
|
+
}
|
|
151
|
+
NSFont *resolved = self_resolveFont(fontId, size);
|
|
152
|
+
if (resolved) {
|
|
153
|
+
@synchronized(cache) { cache[key] = resolved; }
|
|
154
|
+
}
|
|
155
|
+
return resolved;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
static NSFont *self_resolveFont(int fontId, CGFloat size)
|
|
159
|
+
{
|
|
160
|
+
NSString *family = familyForId(fontId);
|
|
161
|
+
if (!family || family.length == 0) return [NSFont systemFontOfSize:size];
|
|
162
|
+
// CSS system-font keywords map to the actual system UI font — San Francisco
|
|
163
|
+
// on macOS. There is no PostScript face literally named "-apple-system", so
|
|
164
|
+
// `+[NSFont fontWithName:@"-apple-system"]` returns nil and we'd otherwise
|
|
165
|
+
// fall through to a wrong family. systemFontOfSize: IS San Francisco.
|
|
166
|
+
NSString *lower = family.lowercaseString;
|
|
167
|
+
if ([lower isEqualToString:@"-apple-system"] || [lower isEqualToString:@"system-ui"] ||
|
|
168
|
+
[lower isEqualToString:@"blinkmacsystemfont"] || [lower hasPrefix:@".applesystemuifont"] ||
|
|
169
|
+
[lower hasPrefix:@".sf"] || [lower isEqualToString:@"san francisco"] ||
|
|
170
|
+
[lower isEqualToString:@"sf pro"] || [lower isEqualToString:@"sf pro text"]) {
|
|
171
|
+
return [NSFont systemFontOfSize:size];
|
|
172
|
+
}
|
|
173
|
+
NSFont *font = [NSFont fontWithName:family size:size];
|
|
174
|
+
if (font) return font;
|
|
175
|
+
// CTFont's family lookup is more forgiving than NSFont's PostScript-name
|
|
176
|
+
// lookup — fall back to building a CTFont with the family attribute.
|
|
177
|
+
NSDictionary *attrs = @{
|
|
178
|
+
(__bridge NSString *)kCTFontFamilyNameAttribute: family,
|
|
179
|
+
(__bridge NSString *)kCTFontSizeAttribute: @(size),
|
|
180
|
+
};
|
|
181
|
+
CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attrs);
|
|
182
|
+
CTFontRef ct = CTFontCreateWithFontDescriptor(desc, size, NULL);
|
|
183
|
+
CFRelease(desc);
|
|
184
|
+
if (ct) {
|
|
185
|
+
NSFont *resolved = (__bridge_transfer NSFont *)ct;
|
|
186
|
+
return resolved;
|
|
187
|
+
}
|
|
188
|
+
return [NSFont systemFontOfSize:size];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
int registerCustomTtfDirectory(NSString *directoryPath)
|
|
192
|
+
{
|
|
193
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
194
|
+
BOOL isDir = NO;
|
|
195
|
+
if (![fm fileExistsAtPath:directoryPath isDirectory:&isDir] || !isDir) return 0;
|
|
196
|
+
NSArray<NSString *> *entries = [fm contentsOfDirectoryAtPath:directoryPath error:nil];
|
|
197
|
+
int count = 0;
|
|
198
|
+
for (NSString *name in entries) {
|
|
199
|
+
NSString *lower = name.lowercaseString;
|
|
200
|
+
if (![lower hasSuffix:@".ttf"] && ![lower hasSuffix:@".otf"]) continue;
|
|
201
|
+
NSString *path = [directoryPath stringByAppendingPathComponent:name];
|
|
202
|
+
registerTtfBytesForFamily(path);
|
|
203
|
+
CFErrorRef err = NULL;
|
|
204
|
+
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:path];
|
|
205
|
+
if (CTFontManagerRegisterFontsForURL(url, kCTFontManagerScopeProcess, &err)) {
|
|
206
|
+
count++;
|
|
207
|
+
} else if (err) {
|
|
208
|
+
NSError *e = (__bridge_transfer NSError *)err;
|
|
209
|
+
// kCTFontManagerErrorAlreadyRegistered == 105 — fine on rebuilds.
|
|
210
|
+
if (e.code != 105) {
|
|
211
|
+
NSLog(@"[gea] failed to register font %@: %@", name, e.localizedDescription);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return count;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
} // namespace gea::macos
|
|
219
|
+
|
|
220
|
+
// Strong overrides of the framework's weak font-lookup hooks for the AppKit
|
|
221
|
+
// renderer. The Thermalright target uses Gea's generated raster font atlas
|
|
222
|
+
// instead, so these host-font overrides must stay out of that build.
|
|
223
|
+
#if !GEA_MACOS_THERMALRIGHT_DISPLAY_TARGET
|
|
224
|
+
namespace gea::framework::graphics::generated {
|
|
225
|
+
|
|
226
|
+
int lookupFontFamily(const char *family)
|
|
227
|
+
{
|
|
228
|
+
if (!family || !family[0]) return -1;
|
|
229
|
+
return gea::macos::registerFamily(std::string(family));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const RasterizedFontData *lookupFontForFamily(int, int) { return nullptr; }
|
|
233
|
+
const RasterizedFontData *lookupFont(int) { return nullptr; }
|
|
234
|
+
void ensureLinked() {}
|
|
235
|
+
|
|
236
|
+
// Feeds the engine's runtime TTF rasterizer (canvas ctx.fillText). AppKit
|
|
237
|
+
// text nodes never come through here — they resolve native NSFonts via
|
|
238
|
+
// fontForId above.
|
|
239
|
+
const std::uint8_t *lookupRuntimeTtfFontForFamily(int familyId, unsigned long *length)
|
|
240
|
+
{
|
|
241
|
+
return gea::macos::runtimeTtfBytesForFamilyId(familyId, length);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
} // namespace gea::framework::graphics::generated
|
|
245
|
+
#endif
|
|
246
|
+
|
|
247
|
+
namespace gea::macos {
|
|
248
|
+
|
|
249
|
+
// Choose a line-break mode that breaks AT word boundaries when possible,
|
|
250
|
+
// falling back to mid-character when an unbreakable run is wider than the
|
|
251
|
+
// available width. The renderer and the measurement path share this logic
|
|
252
|
+
// so AppKit's render geometry always matches the framework's measurement.
|
|
253
|
+
NSLineBreakMode lineBreakModeForText(NSString *text, NSFont *font, CGFloat maxWidth)
|
|
254
|
+
{
|
|
255
|
+
if (maxWidth <= 0 || text.length == 0 || !font) return NSLineBreakByWordWrapping;
|
|
256
|
+
NSDictionary *probeAttrs = @{ NSFontAttributeName: font };
|
|
257
|
+
// Split on whitespace and check each chunk individually. If even one
|
|
258
|
+
// "word" wouldn't fit on a line by itself, word-wrap can't help us —
|
|
259
|
+
// we'd render that word overflowing the field width and the parent
|
|
260
|
+
// tile's overflow:hidden would clip it mid-glyph with no indication.
|
|
261
|
+
// Char-wrap breaks inside the word so all of it stays visible.
|
|
262
|
+
NSArray<NSString *> *chunks = [text componentsSeparatedByCharactersInSet:
|
|
263
|
+
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
264
|
+
for (NSString *chunk in chunks) {
|
|
265
|
+
if (chunk.length == 0) continue;
|
|
266
|
+
CGFloat chunkWidth = [chunk sizeWithAttributes:probeAttrs].width;
|
|
267
|
+
if (chunkWidth > maxWidth) return NSLineBreakByCharWrapping;
|
|
268
|
+
}
|
|
269
|
+
return NSLineBreakByWordWrapping;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} // namespace gea::macos
|
|
273
|
+
|
|
274
|
+
// Host text-measurement hook for the AppKit renderer. The Thermalright target
|
|
275
|
+
// has no AppKit text renderer; it wants the framework's generated-font metrics.
|
|
276
|
+
#if !GEA_MACOS_THERMALRIGHT_DISPLAY_TARGET
|
|
277
|
+
extern "C" bool gea_host_measure_text(const char *text,
|
|
278
|
+
int maxWidth,
|
|
279
|
+
int fontId,
|
|
280
|
+
int fontSize,
|
|
281
|
+
int *outWidth,
|
|
282
|
+
int *outHeight)
|
|
283
|
+
{
|
|
284
|
+
if (!outWidth || !outHeight) return false;
|
|
285
|
+
if (!text || !text[0]) {
|
|
286
|
+
*outWidth = 0;
|
|
287
|
+
*outHeight = 0;
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Memoize by (text, maxWidth, fontId, fontSize): the layout engine re-measures
|
|
292
|
+
// EVERY text node on EVERY layout pass, and a pass now runs on every
|
|
293
|
+
// interaction. NSTextFieldCell.cellSizeForBounds:/sizeWithAttributes:
|
|
294
|
+
// dominated the interaction profile; the inputs fully determine the result,
|
|
295
|
+
// so a cache turns repeat measurements (the stable note list) into O(1).
|
|
296
|
+
static std::mutex measureLock;
|
|
297
|
+
static std::unordered_map<std::string, std::pair<int, int>> measureCache;
|
|
298
|
+
std::string key;
|
|
299
|
+
key.reserve(std::strlen(text) + 24);
|
|
300
|
+
key.append(text);
|
|
301
|
+
key.push_back('\x1f');
|
|
302
|
+
key.append(std::to_string(maxWidth));
|
|
303
|
+
key.push_back('\x1f');
|
|
304
|
+
key.append(std::to_string(fontId));
|
|
305
|
+
key.push_back('\x1f');
|
|
306
|
+
key.append(std::to_string(fontSize));
|
|
307
|
+
{
|
|
308
|
+
std::scoped_lock guard(measureLock);
|
|
309
|
+
auto it = measureCache.find(key);
|
|
310
|
+
if (it != measureCache.end()) {
|
|
311
|
+
*outWidth = it->second.first;
|
|
312
|
+
*outHeight = it->second.second;
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
NSFont *font = gea::macos::fontForId(fontId, fontSize);
|
|
318
|
+
if (!font) return false;
|
|
319
|
+
NSString *str = [NSString stringWithUTF8String:text] ?: @"";
|
|
320
|
+
const CGFloat boundedMax = maxWidth > 0 ? maxWidth : CGFLOAT_MAX;
|
|
321
|
+
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
|
|
322
|
+
paragraph.lineBreakMode = gea::macos::lineBreakModeForText(str, font, boundedMax);
|
|
323
|
+
NSDictionary *attrs = @{
|
|
324
|
+
NSFontAttributeName: font,
|
|
325
|
+
NSParagraphStyleAttributeName: paragraph,
|
|
326
|
+
};
|
|
327
|
+
NSAttributedString *attributed = [[NSAttributedString alloc] initWithString:str attributes:attrs];
|
|
328
|
+
// Use an actual NSTextFieldCell to measure: this is exactly what the
|
|
329
|
+
// renderer's NSTextField uses to lay text out, so the size we report
|
|
330
|
+
// here is the size AppKit will draw into — no off-by-side-bearing,
|
|
331
|
+
// no kerning surprise, no device-metrics vs typographic gap. Sizing
|
|
332
|
+
// against a fresh cell of size (maxWidth, large) returns the wrapped
|
|
333
|
+
// height + the actual line width AppKit needs.
|
|
334
|
+
NSTextFieldCell *cell = [[NSTextFieldCell alloc] init];
|
|
335
|
+
cell.bezeled = NO;
|
|
336
|
+
cell.bordered = NO;
|
|
337
|
+
cell.drawsBackground = NO;
|
|
338
|
+
cell.wraps = YES;
|
|
339
|
+
cell.attributedStringValue = attributed;
|
|
340
|
+
const NSSize cellSize = [cell cellSizeForBounds:NSMakeRect(0, 0, boundedMax, CGFLOAT_MAX)];
|
|
341
|
+
*outWidth = static_cast<int>(std::ceil(cellSize.width));
|
|
342
|
+
*outHeight = static_cast<int>(std::ceil(cellSize.height));
|
|
343
|
+
{
|
|
344
|
+
std::scoped_lock guard(measureLock);
|
|
345
|
+
if (measureCache.size() > 8192) measureCache.clear(); // bound unbounded growth
|
|
346
|
+
measureCache.emplace(std::move(key), std::make_pair(*outWidth, *outHeight));
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
#endif
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#ifdef __OBJC__
|
|
4
|
+
@class NSImage;
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
namespace gea::macos {
|
|
8
|
+
|
|
9
|
+
#ifdef __OBJC__
|
|
10
|
+
// Returns an NSImage built from the ImageStore slot at imageId, or nil if
|
|
11
|
+
// the slot is empty / invalid. Pixels are converted from RGB565 (+ optional
|
|
12
|
+
// alpha channel) to RGBA8888. Caller owns the result via ARC.
|
|
13
|
+
NSImage *imageForId(int imageId);
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
} // namespace gea::macos
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#import <AppKit/AppKit.h>
|
|
2
|
+
|
|
3
|
+
#include "image_bridge.h"
|
|
4
|
+
#include "image.h"
|
|
5
|
+
#include "pixel.h"
|
|
6
|
+
|
|
7
|
+
namespace gea::macos {
|
|
8
|
+
|
|
9
|
+
NSImage *imageForId(int imageId)
|
|
10
|
+
{
|
|
11
|
+
using namespace gea::framework::graphics;
|
|
12
|
+
if (imageId < 0) return nil;
|
|
13
|
+
auto &store = ImageStore::instance();
|
|
14
|
+
const int w = store.width(imageId);
|
|
15
|
+
const int h = store.height(imageId);
|
|
16
|
+
const std::uint16_t *src = store.currentPixels(imageId);
|
|
17
|
+
const std::uint8_t *alpha = store.currentAlpha(imageId);
|
|
18
|
+
if (w <= 0 || h <= 0 || !src) return nil;
|
|
19
|
+
|
|
20
|
+
const NSInteger pixelCount = w * h;
|
|
21
|
+
NSMutableData *rgba = [NSMutableData dataWithLength:(NSUInteger)(pixelCount * 4)];
|
|
22
|
+
std::uint8_t *dst = static_cast<std::uint8_t *>(rgba.mutableBytes);
|
|
23
|
+
for (NSInteger i = 0; i < pixelCount; i++) {
|
|
24
|
+
int r, g, b;
|
|
25
|
+
pixel::unpackRgb565(src[i], &r, &g, &b);
|
|
26
|
+
dst[i * 4 + 0] = static_cast<std::uint8_t>((r * 255 + 15) / 31);
|
|
27
|
+
dst[i * 4 + 1] = static_cast<std::uint8_t>((g * 255 + 31) / 63);
|
|
28
|
+
dst[i * 4 + 2] = static_cast<std::uint8_t>((b * 255 + 15) / 31);
|
|
29
|
+
dst[i * 4 + 3] = alpha ? alpha[i] : 0xFF;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
NSBitmapImageRep *rep =
|
|
33
|
+
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
34
|
+
pixelsWide:w
|
|
35
|
+
pixelsHigh:h
|
|
36
|
+
bitsPerSample:8
|
|
37
|
+
samplesPerPixel:4
|
|
38
|
+
hasAlpha:YES
|
|
39
|
+
isPlanar:NO
|
|
40
|
+
colorSpaceName:NSDeviceRGBColorSpace
|
|
41
|
+
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
|
42
|
+
bytesPerRow:w * 4
|
|
43
|
+
bitsPerPixel:32];
|
|
44
|
+
if (!rep) return nil;
|
|
45
|
+
memcpy([rep bitmapData], dst, (size_t)(pixelCount * 4));
|
|
46
|
+
|
|
47
|
+
NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)];
|
|
48
|
+
[img addRepresentation:rep];
|
|
49
|
+
return img;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
} // namespace gea::macos
|