basalt-win32 0.0.0-rc5

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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/cli/runWindows.js +35 -0
  3. package/cli/shared.js +75 -0
  4. package/native/CMakeLists.txt +398 -0
  5. package/native/tests/test_win32_accessibility.cpp +271 -0
  6. package/native/tests/test_win32_clipboard.cpp +121 -0
  7. package/native/tests/test_win32_focus.cpp +195 -0
  8. package/native/tests/test_win32_hittest.cpp +410 -0
  9. package/native/tests/test_win32_image.cpp +239 -0
  10. package/native/tests/test_win32_imageloader.cpp +208 -0
  11. package/native/tests/test_win32_input.cpp +361 -0
  12. package/native/tests/test_win32_key_events.cpp +84 -0
  13. package/native/tests/test_win32_mounting.cpp +410 -0
  14. package/native/tests/test_win32_paint.cpp +505 -0
  15. package/native/tests/test_win32_scrollview.cpp +385 -0
  16. package/native/tests/test_win32_text.cpp +258 -0
  17. package/native/tests/test_win32_textinput.cpp +609 -0
  18. package/native/tests/test_win32_titlebar.cpp +111 -0
  19. package/native/tests/test_win32_titlebar_actions.cpp +62 -0
  20. package/native/tests/test_win32_view.cpp +222 -0
  21. package/native/win32/ColorSchemeWin32.cpp +56 -0
  22. package/native/win32/ComponentRegistryWin32.cpp +124 -0
  23. package/native/win32/DirectWriteLayout.cpp +116 -0
  24. package/native/win32/DirectWriteLayout.h +36 -0
  25. package/native/win32/DirectWriteLayoutManager.cpp +124 -0
  26. package/native/win32/FontRegistryDirectWrite.cpp +117 -0
  27. package/native/win32/PlatformServicesWin32.cpp +643 -0
  28. package/native/win32/RnWin32Accessible.cpp +258 -0
  29. package/native/win32/RnWin32Accessible.h +91 -0
  30. package/native/win32/RnWin32Image.cpp +265 -0
  31. package/native/win32/RnWin32Image.h +93 -0
  32. package/native/win32/RnWin32TextLayout.cpp +310 -0
  33. package/native/win32/RnWin32TextLayout.h +160 -0
  34. package/native/win32/RnWin32View.cpp +1281 -0
  35. package/native/win32/RnWin32View.h +506 -0
  36. package/native/win32/Win32AnimationChoreographer.cpp +76 -0
  37. package/native/win32/Win32AnimationChoreographer.h +61 -0
  38. package/native/win32/Win32Clip.h +173 -0
  39. package/native/win32/Win32DropTarget.cpp +567 -0
  40. package/native/win32/Win32DropTarget.h +42 -0
  41. package/native/win32/Win32Focus.cpp +262 -0
  42. package/native/win32/Win32Focus.h +109 -0
  43. package/native/win32/Win32ImageLoader.cpp +167 -0
  44. package/native/win32/Win32ImageLoader.h +104 -0
  45. package/native/win32/Win32KeyEvents.cpp +126 -0
  46. package/native/win32/Win32KeyEvents.h +20 -0
  47. package/native/win32/Win32MenuBar.cpp +352 -0
  48. package/native/win32/Win32MenuBar.h +16 -0
  49. package/native/win32/Win32MountingManager.cpp +727 -0
  50. package/native/win32/Win32MountingManager.h +203 -0
  51. package/native/win32/Win32Packaging.cpp +161 -0
  52. package/native/win32/Win32Packaging.h +56 -0
  53. package/native/win32/Win32RunLoopObserver.cpp +48 -0
  54. package/native/win32/Win32RunLoopObserver.h +42 -0
  55. package/native/win32/Win32ScrollView.cpp +515 -0
  56. package/native/win32/Win32ScrollView.h +208 -0
  57. package/native/win32/Win32SkiaContext.cpp +221 -0
  58. package/native/win32/Win32SkiaContext.h +53 -0
  59. package/native/win32/Win32SkiaModule.cpp +80 -0
  60. package/native/win32/Win32SkiaModule.h +50 -0
  61. package/native/win32/Win32SkiaPeer.cpp +358 -0
  62. package/native/win32/Win32SkiaPeer.h +66 -0
  63. package/native/win32/Win32SkiaUnicode.cpp +42 -0
  64. package/native/win32/Win32Snapshot.cpp +201 -0
  65. package/native/win32/Win32Snapshot.h +66 -0
  66. package/native/win32/Win32Strings.h +54 -0
  67. package/native/win32/Win32TextInput.cpp +811 -0
  68. package/native/win32/Win32TextInput.h +232 -0
  69. package/native/win32/Win32TitleBar.cpp +562 -0
  70. package/native/win32/Win32TitleBar.h +146 -0
  71. package/native/win32/Win32TitleBarLayout.cpp +46 -0
  72. package/native/win32/Win32TitleBarLayout.h +58 -0
  73. package/native/win32/Win32TouchDispatcher.cpp +443 -0
  74. package/native/win32/Win32TouchDispatcher.h +153 -0
  75. package/native/win32/Win32UiThread.h +27 -0
  76. package/native/win32/Win32WindowControl.cpp +329 -0
  77. package/native/win32/Win32WindowModule.cpp +209 -0
  78. package/native/win32/Win32WindowModule.h +72 -0
  79. package/native/win32/demo_layout_win32.cpp +232 -0
  80. package/native/win32/main_win32.cpp +2481 -0
  81. package/native/win32/mount_harness_win32.cpp +316 -0
  82. package/package.json +26 -0
  83. package/react-native.config.js +22 -0
@@ -0,0 +1,258 @@
1
+ #include "RnWin32Accessible.h"
2
+
3
+ #include "Win32Strings.h"
4
+
5
+ #include <windows.h>
6
+
7
+ // WIN32_LEAN_AND_MEAN keeps combaseapi.h out of windows.h, and that is where the
8
+ // `interface` macro lives -- without it UIAutomationCore.h fails on its own
9
+ // first typedef, which reads as a broken SDK rather than a missing include.
10
+ #include <objbase.h>
11
+
12
+ #include <uiautomation.h>
13
+
14
+ #include <atomic>
15
+ #include <unordered_map>
16
+
17
+ namespace basalt::win32 {
18
+ namespace {
19
+
20
+ // React Native's role vocabulary, mapped onto UIA control types. The names are
21
+ // exactly the ones `GtkMountingManager.cpp` accepts, in the same order, because
22
+ // a role this platform silently ignores and GTK honours is a difference no test
23
+ // comparing two trees would catch -- both print React Native's own string.
24
+ //
25
+ // Three of them are approximations UIA forces, and they are worth naming rather
26
+ // than leaving to be rediscovered. UIA has no switch, so a switch is a checkbox
27
+ // with a toggle state, which is what Narrator reads sensibly. It has no heading
28
+ // control type either -- headings are a *property*, UIA_HeadingLevelPropertyId,
29
+ // on a text element -- so "header" is text. And "alert" is not a control type
30
+ // at all in UIA; it is an event, so the nearest honest resting place is a group
31
+ // until there is a live-region implementation to raise it from.
32
+ const std::unordered_map<std::string, long> &roleTable() {
33
+ static const std::unordered_map<std::string, long> kRoles = {
34
+ {"button", UIA_ButtonControlTypeId},
35
+ {"togglebutton", UIA_ButtonControlTypeId},
36
+ {"link", UIA_HyperlinkControlTypeId},
37
+ {"search", UIA_EditControlTypeId},
38
+ {"image", UIA_ImageControlTypeId},
39
+ {"imagebutton", UIA_ButtonControlTypeId},
40
+ {"text", UIA_TextControlTypeId},
41
+ {"header", UIA_TextControlTypeId},
42
+ {"adjustable", UIA_SliderControlTypeId},
43
+ {"alert", UIA_GroupControlTypeId},
44
+ {"checkbox", UIA_CheckBoxControlTypeId},
45
+ {"combobox", UIA_ComboBoxControlTypeId},
46
+ {"menu", UIA_MenuControlTypeId},
47
+ {"menubar", UIA_MenuBarControlTypeId},
48
+ {"menuitem", UIA_MenuItemControlTypeId},
49
+ {"progressbar", UIA_ProgressBarControlTypeId},
50
+ {"radio", UIA_RadioButtonControlTypeId},
51
+ {"radiogroup", UIA_GroupControlTypeId},
52
+ {"scrollbar", UIA_ScrollBarControlTypeId},
53
+ {"spinbutton", UIA_SpinnerControlTypeId},
54
+ {"switch", UIA_CheckBoxControlTypeId},
55
+ {"tab", UIA_TabItemControlTypeId},
56
+ {"tablist", UIA_TabControlTypeId},
57
+ {"list", UIA_ListControlTypeId},
58
+ {"grid", UIA_DataGridControlTypeId},
59
+ {"toolbar", UIA_ToolBarControlTypeId},
60
+ {"tooltip", UIA_ToolTipControlTypeId},
61
+ };
62
+ return kRoles;
63
+ }
64
+
65
+ BSTR toBstr(const std::string &text) {
66
+ const std::wstring wide = widen(text);
67
+ return SysAllocStringLen(wide.c_str(), static_cast<UINT>(wide.size()));
68
+ }
69
+
70
+ // The property half of a UIA provider.
71
+ //
72
+ // ProviderOptions_ServerSideProvider because this lives in the process that
73
+ // owns the UI, which is the only arrangement that makes sense for a host that
74
+ // draws its own views.
75
+ class AccessibleProvider final : public IRawElementProviderSimple {
76
+ public:
77
+ explicit AccessibleProvider(const RnAccessibleInfo &info) : info_(info) {}
78
+
79
+ // --- IUnknown ---
80
+
81
+ ULONG STDMETHODCALLTYPE AddRef() override { return ++references_; }
82
+
83
+ ULONG STDMETHODCALLTYPE Release() override {
84
+ const ULONG remaining = --references_;
85
+ if (remaining == 0) {
86
+ delete this;
87
+ }
88
+ return remaining;
89
+ }
90
+
91
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **object) override {
92
+ if (object == nullptr) {
93
+ return E_INVALIDARG;
94
+ }
95
+ if (riid == __uuidof(IUnknown) || riid == __uuidof(IRawElementProviderSimple)) {
96
+ *object = static_cast<IRawElementProviderSimple *>(this);
97
+ AddRef();
98
+ return S_OK;
99
+ }
100
+ *object = nullptr;
101
+ return E_NOINTERFACE;
102
+ }
103
+
104
+ // --- IRawElementProviderSimple ---
105
+
106
+ HRESULT STDMETHODCALLTYPE get_ProviderOptions(ProviderOptions *options) override {
107
+ if (options == nullptr) {
108
+ return E_INVALIDARG;
109
+ }
110
+ *options = ProviderOptions_ServerSideProvider;
111
+ return S_OK;
112
+ }
113
+
114
+ // The control patterns -- Toggle, SelectionItem, ExpandCollapse, Invoke --
115
+ // land with the host, because a pattern that reports a state but cannot be
116
+ // driven is worse than none: it tells a screen reader the control can be
117
+ // operated and then does nothing. The *states* are answered as properties
118
+ // below, which is read-only and honest.
119
+ HRESULT STDMETHODCALLTYPE GetPatternProvider(PATTERNID, IUnknown **provider) override {
120
+ if (provider == nullptr) {
121
+ return E_INVALIDARG;
122
+ }
123
+ *provider = nullptr;
124
+ return S_OK;
125
+ }
126
+
127
+ HRESULT STDMETHODCALLTYPE GetPropertyValue(PROPERTYID property, VARIANT *value) override {
128
+ if (value == nullptr) {
129
+ return E_INVALIDARG;
130
+ }
131
+ VariantInit(value);
132
+
133
+ switch (property) {
134
+ case UIA_ControlTypePropertyId:
135
+ value->vt = VT_I4;
136
+ value->lVal = controlTypeForRole(info_.role);
137
+ return S_OK;
138
+
139
+ case UIA_NamePropertyId:
140
+ if (!info_.label.empty()) {
141
+ value->vt = VT_BSTR;
142
+ value->bstrVal = toBstr(info_.label);
143
+ }
144
+ return S_OK;
145
+
146
+ case UIA_HelpTextPropertyId:
147
+ // React Native's `accessibilityHint` is "what happens if you do this",
148
+ // which is what UIA calls help text. AppKit puts it in the
149
+ // accessibility description and GTK in the description property; all
150
+ // three are the same idea under three names.
151
+ if (!info_.hint.empty()) {
152
+ value->vt = VT_BSTR;
153
+ value->bstrVal = toBstr(info_.hint);
154
+ }
155
+ return S_OK;
156
+
157
+ case UIA_IsEnabledPropertyId:
158
+ // Unset means enabled, which is UIA's default and React Native's.
159
+ value->vt = VT_BOOL;
160
+ value->boolVal =
161
+ info_.state.disabled == RnAccessibleFlag::True ? VARIANT_FALSE : VARIANT_TRUE;
162
+ return S_OK;
163
+
164
+ case UIA_ToggleToggleStatePropertyId:
165
+ // The property mirror of the Toggle pattern, so the state is reportable
166
+ // before the pattern exists. Left empty when unset, because
167
+ // ToggleState_Off is a claim that this is an unchecked checkbox.
168
+ if (info_.state.checked != RnAccessibleFlag::Unset) {
169
+ value->vt = VT_I4;
170
+ value->lVal = info_.state.checked == RnAccessibleFlag::True ? ToggleState_On
171
+ : ToggleState_Off;
172
+ }
173
+ return S_OK;
174
+
175
+ case UIA_SelectionItemIsSelectedPropertyId:
176
+ if (info_.state.selected != RnAccessibleFlag::Unset) {
177
+ value->vt = VT_BOOL;
178
+ value->boolVal =
179
+ info_.state.selected == RnAccessibleFlag::True ? VARIANT_TRUE : VARIANT_FALSE;
180
+ }
181
+ return S_OK;
182
+
183
+ case UIA_ExpandCollapseExpandCollapseStatePropertyId:
184
+ if (info_.state.expanded != RnAccessibleFlag::Unset) {
185
+ value->vt = VT_I4;
186
+ value->lVal = info_.state.expanded == RnAccessibleFlag::True
187
+ ? ExpandCollapseState_Expanded
188
+ : ExpandCollapseState_Collapsed;
189
+ }
190
+ return S_OK;
191
+
192
+ case UIA_IsOffscreenPropertyId:
193
+ value->vt = VT_BOOL;
194
+ value->boolVal = info_.hidden ? VARIANT_TRUE : VARIANT_FALSE;
195
+ return S_OK;
196
+
197
+ case UIA_IsControlElementPropertyId:
198
+ case UIA_IsContentElementPropertyId:
199
+ value->vt = VT_BOOL;
200
+ value->boolVal = VARIANT_TRUE;
201
+ return S_OK;
202
+
203
+ default:
204
+ break;
205
+ }
206
+
207
+ // VT_EMPTY, which is UIA's "I have no opinion, use the default". Returning
208
+ // an error here instead makes a screen reader treat the whole element as
209
+ // broken rather than as quiet.
210
+ return S_OK;
211
+ }
212
+
213
+ // Null because these views are not native Windows controls: there is no
214
+ // HWND-based provider underneath to defer to. The host's fragment root is
215
+ // where the one real HWND enters the picture.
216
+ HRESULT STDMETHODCALLTYPE
217
+ get_HostRawElementProvider(IRawElementProviderSimple **provider) override {
218
+ if (provider == nullptr) {
219
+ return E_INVALIDARG;
220
+ }
221
+ *provider = nullptr;
222
+ return S_OK;
223
+ }
224
+
225
+ private:
226
+ ~AccessibleProvider() = default;
227
+
228
+ RnAccessibleInfo info_;
229
+ std::atomic<ULONG> references_{1};
230
+ };
231
+
232
+ } // namespace
233
+
234
+ long controlTypeForRole(const std::string &role) {
235
+ const auto &table = roleTable();
236
+ const auto it = table.find(role);
237
+ return it == table.end() ? UIA_GroupControlTypeId : it->second;
238
+ }
239
+
240
+ bool roleIsPresentational(const std::string &role) {
241
+ return role == "none" || role == "presentation";
242
+ }
243
+
244
+ bool isAccessibilityElement(const RnAccessibleInfo &info) {
245
+ if (info.hidden || roleIsPresentational(info.role)) {
246
+ return false;
247
+ }
248
+ return !info.role.empty() || !info.label.empty() || !info.hint.empty();
249
+ }
250
+
251
+ IRawElementProviderSimple *createAccessibleProvider(const RnAccessibleInfo &info) {
252
+ if (!isAccessibilityElement(info)) {
253
+ return nullptr;
254
+ }
255
+ return new AccessibleProvider(info);
256
+ }
257
+
258
+ } // namespace basalt::win32
@@ -0,0 +1,91 @@
1
+ // What a screen reader is told about a view.
2
+ //
3
+ // This is the one place Windows is structurally different from the other two
4
+ // desktops rather than differently spelled. GTK and AppKit both take
5
+ // accessibility as *properties on a view*: `gtk_accessible_update_property`,
6
+ // `setAccessibilityRole:`. UI Automation inverts that -- it asks a *provider*
7
+ // object questions, and the provider answers by property id. So where the other
8
+ // two mounting managers push, this one has to be ready to be pulled.
9
+ //
10
+ // The consequence worth knowing: a GTK role is construct-only, which is why
11
+ // `docs/DECISIONS.md` records that `accessibilityRole` cannot change after
12
+ // mount there. Nothing here has that constraint, because nothing is baked into
13
+ // a widget class -- the provider reads the current value each time it is asked.
14
+ // This platform can therefore do something GTK cannot, and the mounting manager
15
+ // should not copy GTK's restriction when it arrives.
16
+ //
17
+ // What is here is the property half, which is all of it that can exist before
18
+ // there is an HWND: UIA's tree navigation needs a fragment root, and a fragment
19
+ // root is a window. `IRawElementProviderFragment` and the `Toggle`,
20
+ // `SelectionItem` and `ExpandCollapse` *patterns* therefore land with the host.
21
+ // The states those patterns expose are answerable now, because UIA mirrors each
22
+ // of them as a plain property id, and that is what these answer.
23
+
24
+ #pragma once
25
+
26
+ #include <string>
27
+
28
+ struct IRawElementProviderSimple;
29
+
30
+ namespace basalt::win32 {
31
+
32
+ // Each state is a tri-state on purpose. Leaving `checked` unset is not the same
33
+ // as setting it false: a view that never mentions being checked is not an
34
+ // unchecked checkbox, and a screen reader should not read it as one. Both other
35
+ // platforms make the same distinction.
36
+ enum class RnAccessibleFlag {
37
+ Unset,
38
+ False,
39
+ True,
40
+ };
41
+
42
+ struct RnAccessibleState {
43
+ RnAccessibleFlag disabled = RnAccessibleFlag::Unset;
44
+ RnAccessibleFlag checked = RnAccessibleFlag::Unset;
45
+ RnAccessibleFlag selected = RnAccessibleFlag::Unset;
46
+ RnAccessibleFlag expanded = RnAccessibleFlag::Unset;
47
+ RnAccessibleFlag busy = RnAccessibleFlag::Unset;
48
+ };
49
+
50
+ // Everything a view carries for assistive technology. Held by the view as plain
51
+ // data; the provider below reads it.
52
+ struct RnAccessibleInfo {
53
+ // React Native's own role string -- "button", "image", "text" -- not a UIA
54
+ // control type. The mapping is a platform decision and belongs beside the
55
+ // platform, and the string is what `describeTree` reports, so the three
56
+ // hosts' trees compare without one of them speaking UIA's vocabulary and
57
+ // another speaking GTK's.
58
+ std::string role;
59
+ std::string label;
60
+ std::string hint;
61
+ RnAccessibleState state;
62
+ // accessible={false} and accessibilityElementsHidden.
63
+ bool hidden = false;
64
+ };
65
+
66
+ // The UIA control type for a React Native role name, or
67
+ // UIA_GroupControlTypeId for one this platform does not recognise.
68
+ //
69
+ // A fallback rather than a guess: a wrong control type is worse than a vague
70
+ // one, because it makes a view announce itself as something it is not. Both
71
+ // other platforms fall back the same way.
72
+ long controlTypeForRole(const std::string &role);
73
+
74
+ // True for "none" and "presentation", which mean "do not report this at all".
75
+ bool roleIsPresentational(const std::string &role);
76
+
77
+ // True when this view should appear to a screen reader. A plain <View> with no
78
+ // role, no label and no hint is scaffolding, and a tree full of untyped groups
79
+ // is worse for a screen reader user than a tree without them.
80
+ bool isAccessibilityElement(const RnAccessibleInfo &info);
81
+
82
+ // A UI Automation provider over one view's RnAccessibleInfo, for the property
83
+ // half of the interface. Returns null when the view is not an accessibility
84
+ // element. The caller owns a reference and must Release it.
85
+ //
86
+ // The provider copies the info rather than pointing at the view: UIA can ask
87
+ // its questions from another thread and long after the view is gone, and a
88
+ // dangling read there is a crash inside somebody's screen reader.
89
+ IRawElementProviderSimple *createAccessibleProvider(const RnAccessibleInfo &info);
90
+
91
+ } // namespace basalt::win32
@@ -0,0 +1,265 @@
1
+ #include "RnWin32Image.h"
2
+
3
+ #include "Win32Clip.h"
4
+
5
+ #include <windows.h>
6
+
7
+ #include <d2d1.h>
8
+ #include <wincodec.h>
9
+ #include <wrl/client.h>
10
+
11
+ #include <algorithm>
12
+
13
+ using Microsoft::WRL::ComPtr;
14
+
15
+ namespace basalt::win32 {
16
+ namespace {
17
+
18
+ bool ensureCom() {
19
+ const HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
20
+ return SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE;
21
+ }
22
+
23
+ IWICImagingFactory *wicFactory() {
24
+ // Every call, not only the first: decoding happens on whichever worker thread
25
+ // the image loader put it on, and COM is per-thread. The factory itself is
26
+ // agile -- WIC registers it "Both" -- so one instance serves every thread,
27
+ // but a thread that has never called CoInitializeEx cannot use it.
28
+ // CoInitializeEx on an already-initialised thread returns S_FALSE and costs
29
+ // nothing.
30
+ if (!ensureCom()) {
31
+ return nullptr;
32
+ }
33
+ static IWICImagingFactory *factory = [] {
34
+ IWICImagingFactory *created = nullptr;
35
+ CoCreateInstance(
36
+ CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&created));
37
+ return created;
38
+ }();
39
+ return factory;
40
+ }
41
+
42
+ } // namespace
43
+
44
+ const char *imageFitName(RnImageFit fit) {
45
+ switch (fit) {
46
+ case RnImageFit::Contain:
47
+ return "contain";
48
+ case RnImageFit::Stretch:
49
+ return "stretch";
50
+ case RnImageFit::Center:
51
+ return "center";
52
+ case RnImageFit::Repeat:
53
+ return "repeat";
54
+ case RnImageFit::Cover:
55
+ break;
56
+ }
57
+ return "cover";
58
+ }
59
+
60
+ std::shared_ptr<RnWin32Image>
61
+ RnWin32Image::fromPixels(unsigned width, unsigned height, const uint8_t *bgra) {
62
+ IWICImagingFactory *factory = wicFactory();
63
+ if (factory == nullptr || bgra == nullptr || width == 0 || height == 0) {
64
+ return nullptr;
65
+ }
66
+
67
+ const UINT stride = width * 4;
68
+ ComPtr<IWICBitmap> bitmap;
69
+ if (FAILED(factory->CreateBitmapFromMemory(width,
70
+ height,
71
+ GUID_WICPixelFormat32bppPBGRA,
72
+ stride,
73
+ stride * height,
74
+ const_cast<BYTE *>(bgra),
75
+ bitmap.GetAddressOf()))) {
76
+ return nullptr;
77
+ }
78
+
79
+ auto image = std::shared_ptr<RnWin32Image>(new RnWin32Image());
80
+ image->bitmap_ = bitmap.Detach();
81
+ image->width_ = width;
82
+ image->height_ = height;
83
+ return image;
84
+ }
85
+
86
+ std::shared_ptr<RnWin32Image> RnWin32Image::fromEncodedBytes(const uint8_t *data, size_t size) {
87
+ IWICImagingFactory *factory = wicFactory();
88
+ if (factory == nullptr || data == nullptr || size == 0) {
89
+ return nullptr;
90
+ }
91
+
92
+ // A stream over the caller's bytes. WIC copies what it needs during
93
+ // CreateBitmapFromSource below, so the caller's buffer does not have to
94
+ // outlive this function.
95
+ ComPtr<IWICStream> stream;
96
+ if (FAILED(factory->CreateStream(stream.GetAddressOf())) ||
97
+ FAILED(stream->InitializeFromMemory(const_cast<BYTE *>(data), static_cast<DWORD>(size)))) {
98
+ return nullptr;
99
+ }
100
+
101
+ ComPtr<IWICBitmapDecoder> decoder;
102
+ if (FAILED(factory->CreateDecoderFromStream(
103
+ stream.Get(), nullptr, WICDecodeMetadataCacheOnLoad, decoder.GetAddressOf()))) {
104
+ return nullptr;
105
+ }
106
+
107
+ ComPtr<IWICBitmapFrameDecode> frame;
108
+ if (FAILED(decoder->GetFrame(0, frame.GetAddressOf()))) {
109
+ return nullptr;
110
+ }
111
+
112
+ // Whatever the file was, Direct2D wants premultiplied BGRA.
113
+ ComPtr<IWICFormatConverter> converter;
114
+ if (FAILED(factory->CreateFormatConverter(converter.GetAddressOf())) ||
115
+ FAILED(converter->Initialize(frame.Get(),
116
+ GUID_WICPixelFormat32bppPBGRA,
117
+ WICBitmapDitherTypeNone,
118
+ nullptr,
119
+ 0.0,
120
+ WICBitmapPaletteTypeCustom))) {
121
+ return nullptr;
122
+ }
123
+
124
+ ComPtr<IWICBitmap> bitmap;
125
+ if (FAILED(factory->CreateBitmapFromSource(
126
+ converter.Get(), WICBitmapCacheOnLoad, bitmap.GetAddressOf()))) {
127
+ return nullptr;
128
+ }
129
+
130
+ UINT width = 0;
131
+ UINT height = 0;
132
+ if (FAILED(bitmap->GetSize(&width, &height)) || width == 0 || height == 0) {
133
+ return nullptr;
134
+ }
135
+
136
+ auto image = std::shared_ptr<RnWin32Image>(new RnWin32Image());
137
+ image->bitmap_ = bitmap.Detach();
138
+ image->width_ = width;
139
+ image->height_ = height;
140
+ return image;
141
+ }
142
+
143
+ RnWin32Image::~RnWin32Image() {
144
+ if (deviceBitmap_ != nullptr) {
145
+ deviceBitmap_->Release();
146
+ }
147
+ if (bitmap_ != nullptr) {
148
+ bitmap_->Release();
149
+ }
150
+ }
151
+
152
+ void RnWin32Image::draw(ID2D1RenderTarget *target,
153
+ float boxWidth,
154
+ float boxHeight,
155
+ RnImageFit fit,
156
+ const float *tint) const {
157
+ if (target == nullptr || bitmap_ == nullptr || boxWidth <= 0.0f || boxHeight <= 0.0f) {
158
+ return;
159
+ }
160
+
161
+ if (deviceBitmap_ == nullptr || deviceTarget_ != target) {
162
+ if (deviceBitmap_ != nullptr) {
163
+ deviceBitmap_->Release();
164
+ deviceBitmap_ = nullptr;
165
+ }
166
+ ID2D1Bitmap *created = nullptr;
167
+ if (FAILED(target->CreateBitmapFromWicBitmap(bitmap_, nullptr, &created))) {
168
+ return;
169
+ }
170
+ deviceBitmap_ = created;
171
+ deviceTarget_ = target;
172
+ }
173
+
174
+ const float imageWidth = static_cast<float>(width_);
175
+ const float imageHeight = static_cast<float>(height_);
176
+
177
+ // The same arithmetic rn_view_snapshot does on GTK, in the same order, so the
178
+ // three desktops crop and centre identically. A picture is the only thing
179
+ // that would catch a divergence here, which is why test_win32_image.cpp reads
180
+ // pixels rather than checking the destination rect.
181
+ D2D1_RECT_F destination = D2D1::RectF(0.0f, 0.0f, boxWidth, boxHeight);
182
+ if (fit != RnImageFit::Stretch) {
183
+ float scale = 1.0f;
184
+ switch (fit) {
185
+ case RnImageFit::Contain:
186
+ scale = std::min(boxWidth / imageWidth, boxHeight / imageHeight);
187
+ break;
188
+ case RnImageFit::Cover:
189
+ scale = std::max(boxWidth / imageWidth, boxHeight / imageHeight);
190
+ break;
191
+ case RnImageFit::Center:
192
+ // Centred at natural size, but never larger than the frame -- which is
193
+ // what React Native's `center` does.
194
+ scale = std::min(1.0f, std::min(boxWidth / imageWidth, boxHeight / imageHeight));
195
+ break;
196
+ case RnImageFit::Repeat:
197
+ // One tile, at natural size. Where the tiles go is decided below.
198
+ break;
199
+ case RnImageFit::Stretch:
200
+ break;
201
+ }
202
+ const float drawnWidth = imageWidth * scale;
203
+ const float drawnHeight = imageHeight * scale;
204
+ // From the top left for `repeat`, which is what CSS does: the whole tiles
205
+ // start at the origin and the partial one is at the far edge.
206
+ const float x = fit == RnImageFit::Repeat ? 0.0f : (boxWidth - drawnWidth) / 2.0f;
207
+ const float y = fit == RnImageFit::Repeat ? 0.0f : (boxHeight - drawnHeight) / 2.0f;
208
+ destination = D2D1::RectF(x, y, x + drawnWidth, y + drawnHeight);
209
+ }
210
+
211
+ // cover and center can put pixels outside the frame, and an <Image> never
212
+ // paints beyond its own box on iOS or Android. A geometry clip rather than an
213
+ // axis-aligned one, because an <Image> inside a rotated view is not exotic;
214
+ // see Win32Clip.h.
215
+ const bool needsClip =
216
+ fit == RnImageFit::Cover || fit == RnImageFit::Center || fit == RnImageFit::Repeat;
217
+ {
218
+ const ScopedGeometryClip clip(needsClip ? target : nullptr,
219
+ D2D1::RectF(0.0f, 0.0f, boxWidth, boxHeight),
220
+ 0.0f);
221
+ if (fit == RnImageFit::Repeat) {
222
+ // A bitmap brush in wrap mode, which is Direct2D's tiling: the brush
223
+ // repeats the bitmap across whatever is filled, and the clip above is
224
+ // what stops it at the view's edge.
225
+ //
226
+ // Nearest-neighbour on purpose. The default is linear, which blends the
227
+ // last column of one tile into the first of the next and leaves a seam
228
+ // on every boundary -- visible on exactly the small, sharp images people
229
+ // tile.
230
+ Microsoft::WRL::ComPtr<ID2D1BitmapBrush> brush;
231
+ const D2D1_BITMAP_BRUSH_PROPERTIES properties = D2D1::BitmapBrushProperties(
232
+ D2D1_EXTEND_MODE_WRAP,
233
+ D2D1_EXTEND_MODE_WRAP,
234
+ D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
235
+ // The four-argument form on the interface itself rather than one of
236
+ // d2d1.h's inline reference overloads, so which one is selected is not a
237
+ // thing to be wrong about.
238
+ if (SUCCEEDED(target->CreateBitmapBrush(
239
+ deviceBitmap_, &properties, nullptr, brush.GetAddressOf()))) {
240
+ target->FillRectangle(D2D1::RectF(0.0f, 0.0f, boxWidth, boxHeight), brush.Get());
241
+ }
242
+ } else if (tint != nullptr) {
243
+ // The bitmap becomes an opacity mask and the brush is what is painted.
244
+ // Direct2D requires aliased antialiasing for a mask whose content is
245
+ // graphics rather than text, and refuses the call otherwise -- so the
246
+ // mode is changed for the one draw and put back.
247
+ Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
248
+ const D2D1_COLOR_F color = D2D1::ColorF(tint[0], tint[1], tint[2], tint[3]);
249
+ if (SUCCEEDED(target->CreateSolidColorBrush(color, brush.GetAddressOf()))) {
250
+ const D2D1_ANTIALIAS_MODE previous = target->GetAntialiasMode();
251
+ target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
252
+ target->FillOpacityMask(deviceBitmap_,
253
+ brush.Get(),
254
+ D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
255
+ &destination,
256
+ nullptr);
257
+ target->SetAntialiasMode(previous);
258
+ }
259
+ } else {
260
+ target->DrawBitmap(deviceBitmap_, destination);
261
+ }
262
+ }
263
+ }
264
+
265
+ } // namespace basalt::win32