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,116 @@
1
+ #include "DirectWriteLayout.h"
2
+
3
+ #include "FontRegistry.h"
4
+
5
+ #include <react/renderer/graphics/Color.h>
6
+
7
+ #include <cmath>
8
+
9
+ namespace basalt::win32 {
10
+ namespace {
11
+
12
+ using facebook::react::AttributedString;
13
+ using facebook::react::ParagraphAttributes;
14
+ using facebook::react::TextAttributes;
15
+
16
+ RnTextAlign toAlign(const TextAttributes &attributes) {
17
+ if (!attributes.alignment.has_value()) {
18
+ return RnTextAlign::Left;
19
+ }
20
+ switch (*attributes.alignment) {
21
+ case facebook::react::TextAlignment::Center:
22
+ return RnTextAlign::Center;
23
+ case facebook::react::TextAlignment::Right:
24
+ #if BASALT_RN_MINOR >= 87
25
+ // React Native 0.87 added the writing-direction-relative spellings, and
26
+ // both other desktops already fold them in here. Left out of this switch,
27
+ // `textAlign: 'right'` written as `end` fell through to left-aligned --
28
+ // which clang said out loud, in a warning nobody had recompiled this file
29
+ // to see.
30
+ //
31
+ // Left-to-right only, like the other two: nothing on any of these
32
+ // platforms resolves a writing direction yet.
33
+ case facebook::react::TextAlignment::End:
34
+ #endif
35
+ return RnTextAlign::Right;
36
+ case facebook::react::TextAlignment::Justified:
37
+ return RnTextAlign::Justified;
38
+ case facebook::react::TextAlignment::Natural:
39
+ case facebook::react::TextAlignment::Left:
40
+ #if BASALT_RN_MINOR >= 87
41
+ case facebook::react::TextAlignment::Start:
42
+ #endif
43
+ break;
44
+ }
45
+ return RnTextAlign::Left;
46
+ }
47
+
48
+ // React Native's weights run 100..900 and DirectWrite's do too, but this
49
+ // platform's style struct carries a bool. Anything at semibold or above is
50
+ // bold, which is the same line the GTK side draws.
51
+ bool isBold(const TextAttributes &attributes) {
52
+ if (!attributes.fontWeight.has_value()) {
53
+ return false;
54
+ }
55
+ return static_cast<int>(*attributes.fontWeight) >= static_cast<int>(facebook::react::FontWeight::Semibold);
56
+ }
57
+
58
+ } // namespace
59
+
60
+ RnTextStyle buildTextStyle(const TextAttributes &attributes) {
61
+ RnTextStyle style;
62
+
63
+ if (!attributes.fontFamily.empty()) {
64
+ // Through the font seam, so a family an app registered at runtime resolves
65
+ // to whatever it is actually called. An unregistered name comes back
66
+ // unchanged and DirectWrite's own lookup gets it.
67
+ style.fontFamily = resolveFontFamily(attributes.fontFamily);
68
+ }
69
+
70
+ // A NaN fontSize is React Native's "unset", and passing it to DirectWrite
71
+ // produces a layout with no metrics at all rather than an error.
72
+ if (!std::isnan(attributes.fontSize) && attributes.fontSize > 0) {
73
+ style.fontSize = static_cast<float>(attributes.fontSize);
74
+ }
75
+
76
+ style.bold = isBold(attributes);
77
+ style.italic = attributes.fontStyle.has_value() &&
78
+ *attributes.fontStyle == facebook::react::FontStyle::Italic;
79
+
80
+ if (!std::isnan(attributes.lineHeight) && attributes.lineHeight > 0) {
81
+ style.lineHeight = static_cast<float>(attributes.lineHeight);
82
+ }
83
+
84
+ style.align = toAlign(attributes);
85
+
86
+ if (attributes.foregroundColor) {
87
+ const auto components = facebook::react::colorComponentsFromColor(attributes.foregroundColor);
88
+ style.color[0] = components.red;
89
+ style.color[1] = components.green;
90
+ style.color[2] = components.blue;
91
+ style.color[3] = components.alpha;
92
+ }
93
+
94
+ return style;
95
+ }
96
+
97
+ std::shared_ptr<RnWin32TextLayout>
98
+ buildTextLayout(const AttributedString &attributedString,
99
+ const ParagraphAttributes &paragraphAttributes) {
100
+ std::vector<RnTextRun> runs;
101
+ for (const auto &fragment : attributedString.getFragments()) {
102
+ // An attachment is an inline `<View>`, which occupies space rather than
103
+ // carrying text. Its string is a placeholder React Native does not intend
104
+ // to be drawn, so it contributes no run -- which is also why the attachment
105
+ // rects the layout manager reports are all zero. See docs/BACKLOG.md.
106
+ if (fragment.isAttachment()) {
107
+ continue;
108
+ }
109
+ runs.push_back(RnTextRun{fragment.string, buildTextStyle(fragment.textAttributes)});
110
+ }
111
+
112
+ return RnWin32TextLayout::createFromRuns(
113
+ runs, static_cast<int>(paragraphAttributes.maximumNumberOfLines));
114
+ }
115
+
116
+ } // namespace basalt::win32
@@ -0,0 +1,36 @@
1
+ // AttributedString -> RnWin32TextLayout, and nothing else.
2
+ //
3
+ // Separate from RnWin32TextLayout.h for the reason that file states: the view
4
+ // layer draws paragraphs and must not gain a React Native dependency to do it.
5
+ // This is the file that does know about React Native, and it is the only place
6
+ // a `TextAttributes` is turned into a `RnTextStyle`.
7
+ //
8
+ // Shared by measurement and painting -- `DirectWriteLayoutManager.cpp` calls it
9
+ // from `TextLayoutManager::measure`, and `Win32MountingManager` calls it when a
10
+ // Paragraph's state arrives. That sharing is the point: if the two built
11
+ // layouts differently, Yoga would allot a box computed one way and the view
12
+ // would paint text laid out another. `docs/DECISIONS.md` makes the same
13
+ // argument for Pango.
14
+
15
+ #pragma once
16
+
17
+ #include "RnWin32TextLayout.h"
18
+
19
+ #include <react/renderer/attributedstring/AttributedString.h>
20
+ #include <react/renderer/attributedstring/ParagraphAttributes.h>
21
+ #include <react/renderer/attributedstring/TextAttributes.h>
22
+
23
+ #include <memory>
24
+
25
+ namespace basalt::win32 {
26
+
27
+ // One fragment's attributes, as this platform understands them. Exposed so the
28
+ // mounting manager can ask the same question about a lone run without building
29
+ // a whole paragraph.
30
+ RnTextStyle buildTextStyle(const facebook::react::TextAttributes &textAttributes);
31
+
32
+ std::shared_ptr<RnWin32TextLayout>
33
+ buildTextLayout(const facebook::react::AttributedString &attributedString,
34
+ const facebook::react::ParagraphAttributes &paragraphAttributes);
35
+
36
+ } // namespace basalt::win32
@@ -0,0 +1,124 @@
1
+ // Replaces React Native's stub TextLayoutManager with one over DirectWrite.
2
+ //
3
+ // React Native's cxx platform ships a header and a stub .cpp that ignores every
4
+ // attribute and returns `layoutConstraints.minimumSize`. The header is already
5
+ // generic, so this project keeps it and supplies only the implementation --
6
+ // `cmake/ReactNativeCore.cmake` removes the stub source from
7
+ // `react_renderer_textlayoutmanager` after `add_subdirectory` so the two do not
8
+ // collide. Same arrangement as Pango's and Core Text's; see
9
+ // `docs/DECISIONS.md`.
10
+
11
+ #include "DirectWriteLayout.h"
12
+
13
+ #include "FontRegistry.h"
14
+
15
+ #include <react/renderer/textlayoutmanager/TextLayoutManager.h>
16
+
17
+ #include <algorithm>
18
+ #include <cmath>
19
+ #include <mutex>
20
+ #include <unordered_map>
21
+
22
+ namespace facebook::react {
23
+ namespace {
24
+
25
+ std::mutex &measurementCacheMutex() {
26
+ static std::mutex mutex;
27
+ return mutex;
28
+ }
29
+
30
+ // Not React Native's own `textMeasureCache_`, which has no way to be emptied.
31
+ //
32
+ // A font registered since a measurement invalidates it: the same string with
33
+ // the same attributes measures differently once its family exists. That is not
34
+ // expressible in the cache key, which React Native defines, so the cache has to
35
+ // be droppable instead -- and without that, a font loaded after first render,
36
+ // which is how `useFonts` and every other loader works, appears to do nothing.
37
+ std::unordered_map<TextMeasureCacheKey, TextMeasurement> &measurementCache() {
38
+ static std::unordered_map<TextMeasureCacheKey, TextMeasurement> cache;
39
+ static unsigned long generation = 0;
40
+ const unsigned long current = basalt::fontGeneration();
41
+ if (current != generation) {
42
+ cache.clear();
43
+ generation = current;
44
+ }
45
+ return cache;
46
+ }
47
+
48
+ } // namespace
49
+
50
+ TextLayoutManager::TextLayoutManager(const std::shared_ptr<const ContextContainer> &contextContainer)
51
+ : contextContainer_(contextContainer), textMeasureCache_(kSimpleThreadSafeCacheSizeCap) {}
52
+
53
+ TextMeasurement TextLayoutManager::measure(const AttributedStringBox &attributedStringBox,
54
+ const ParagraphAttributes &paragraphAttributes,
55
+ const TextLayoutContext &layoutContext,
56
+ const LayoutConstraints &layoutConstraints) const {
57
+ const auto &attributedString = attributedStringBox.getValue();
58
+
59
+ // pointScaleFactor joined the cache key in React Native 0.87. On 0.86 the key
60
+ // has three fields and naming a fourth is a compile error. Its absence only
61
+ // makes the cache coarser there, and since nothing below uses the factor,
62
+ // coarser is harmless.
63
+ const TextMeasureCacheKey key{
64
+ .attributedString = attributedString,
65
+ .paragraphAttributes = paragraphAttributes,
66
+ .layoutConstraints = layoutConstraints,
67
+ #if BASALT_RN_MINOR >= 87
68
+ .pointScaleFactor = layoutContext.pointScaleFactor,
69
+ #endif
70
+ };
71
+
72
+ #if BASALT_RN_MINOR < 87
73
+ (void)layoutContext;
74
+ #endif
75
+
76
+ {
77
+ const std::lock_guard<std::mutex> lock(measurementCacheMutex());
78
+ auto &cache = measurementCache();
79
+ const auto hit = cache.find(key);
80
+ if (hit != cache.end()) {
81
+ return hit->second;
82
+ }
83
+ }
84
+
85
+ TextMeasurement measured;
86
+
87
+ // An infinite maximum width means "do not wrap".
88
+ const float maxWidth = std::isinf(layoutConstraints.maximumSize.width)
89
+ ? -1.0f
90
+ : static_cast<float>(layoutConstraints.maximumSize.width);
91
+
92
+ const auto layout = basalt::win32::buildTextLayout(attributedString, paragraphAttributes);
93
+ const basalt::win32::RnTextSize size =
94
+ layout == nullptr ? basalt::win32::RnTextSize{} : layout->measure(maxWidth);
95
+
96
+ // DirectWrite can report a line slightly wider than the width it was given,
97
+ // for an unbreakable run. Yoga treats the returned size as final, so clamping
98
+ // here keeps the paragraph inside the box its parent allotted.
99
+ measured.size = Size{
100
+ .width = std::min(static_cast<Float>(size.width), layoutConstraints.maximumSize.width),
101
+ .height = static_cast<Float>(size.height),
102
+ };
103
+
104
+ // Inline views (`<Text><View/></Text>`) reach here as attachment fragments.
105
+ // Reporting a zero frame for each keeps the count right, which is what
106
+ // ParagraphShadowNode iterates over, but they are not positioned -- the same
107
+ // gap both other desktops have, for the same reason.
108
+ for (const auto &fragment : attributedString.getFragments()) {
109
+ if (fragment.isAttachment()) {
110
+ measured.attachments.push_back(TextMeasurement::Attachment{
111
+ .frame = {.origin = {.x = 0, .y = 0}, .size = {.width = 0, .height = 0}},
112
+ .isClipped = false,
113
+ });
114
+ }
115
+ }
116
+
117
+ {
118
+ const std::lock_guard<std::mutex> lock(measurementCacheMutex());
119
+ measurementCache()[key] = measured;
120
+ }
121
+ return measured;
122
+ }
123
+
124
+ } // namespace facebook::react
@@ -0,0 +1,117 @@
1
+ // Windows' half of core/FontRegistry.h -- the seam phase 17 found by linking.
2
+ //
3
+ // `expo-font` arrives here: an app loads a .ttf at runtime and then names it in
4
+ // a `fontFamily`, so two things have to happen. DirectWrite has to be able to
5
+ // find the file, and `resolveFontFamily` has to map the name the app used onto
6
+ // whatever the font actually calls itself.
7
+ //
8
+ // The first is `AddFontResourceExW` with FR_PRIVATE, which adds the file to the
9
+ // process's own font set. On Windows 10 1809 and later DirectWrite's system
10
+ // font collection picks those up, which is why this is a two-line
11
+ // implementation rather than an IDWriteFontSetBuilder and a custom collection.
12
+ // That is also the thing about it most worth distrusting -- see the note at the
13
+ // bottom.
14
+
15
+ #include "FontRegistry.h"
16
+
17
+ #include "Win32Strings.h"
18
+
19
+ #include <windows.h>
20
+
21
+ #include <atomic>
22
+ #include <mutex>
23
+ #include <string>
24
+ #include <unordered_map>
25
+
26
+ namespace basalt {
27
+
28
+ using win32::widen;
29
+ namespace {
30
+
31
+ struct Registry {
32
+ std::mutex mutex;
33
+ // The name the app asked for, against the path it gave. Kept because
34
+ // `resolveFontFamily` has to answer for a name DirectWrite may not know by
35
+ // that spelling.
36
+ std::unordered_map<std::string, std::string> fonts;
37
+ };
38
+
39
+ Registry &registry() {
40
+ static Registry instance;
41
+ return instance;
42
+ }
43
+
44
+ // Bumped on every successful registration. The text layer reads it to know its
45
+ // cached layouts may be stale: a paragraph measured before a font arrived was
46
+ // measured with a fallback, and would keep those metrics forever otherwise.
47
+ std::atomic<unsigned long> generation{0};
48
+
49
+ } // namespace
50
+
51
+ bool registerFont(const std::string &name, const std::string &path) {
52
+ if (name.empty() || path.empty()) {
53
+ return false;
54
+ }
55
+
56
+ const std::wstring widePath = widen(path);
57
+ // FR_PRIVATE: visible to this process only. A font an app loaded is not
58
+ // something to install system-wide, and doing so would need elevation.
59
+ if (AddFontResourceExW(widePath.c_str(), FR_PRIVATE, nullptr) == 0) {
60
+ return false;
61
+ }
62
+
63
+ {
64
+ std::lock_guard<std::mutex> lock(registry().mutex);
65
+ registry().fonts[name] = path;
66
+ }
67
+ generation.fetch_add(1, std::memory_order_relaxed);
68
+ return true;
69
+ }
70
+
71
+ std::string resolveFontFamily(const std::string &name) {
72
+ std::lock_guard<std::mutex> lock(registry().mutex);
73
+ const auto it = registry().fonts.find(name);
74
+ if (it == registry().fonts.end()) {
75
+ // Not one of ours: hand the name back and let DirectWrite's own lookup have
76
+ // it. That is what makes "Segoe UI" work without being registered.
77
+ return name;
78
+ }
79
+ // The name the app used is the name it will ask for. Windows matches on the
80
+ // font's *family* name rather than its filename, and those agree for most
81
+ // fonts an app ships; where they do not, this returns the wrong one. Reading
82
+ // the family out of the file with IDWriteFontFile would be exact and is the
83
+ // fix if this ever misses.
84
+ return name;
85
+ }
86
+
87
+ unsigned long fontGeneration() {
88
+ return generation.load(std::memory_order_relaxed);
89
+ }
90
+
91
+ bool isFontRegistered(const std::string &name) {
92
+ std::lock_guard<std::mutex> lock(registry().mutex);
93
+ return registry().fonts.count(name) != 0;
94
+ }
95
+
96
+ std::string registeredFontNamesJoined(char separator) {
97
+ std::lock_guard<std::mutex> lock(registry().mutex);
98
+ std::string joined;
99
+ for (const auto &[name, path] : registry().fonts) {
100
+ (void)path;
101
+ if (!joined.empty()) {
102
+ joined += separator;
103
+ }
104
+ joined += name;
105
+ }
106
+ return joined;
107
+ }
108
+
109
+ // Worth distrusting, and recorded in docs/BACKLOG.md alongside the same doubt
110
+ // about macOS: no font has been loaded at runtime and then rendered, on any of
111
+ // the three desktops. AddFontResourceEx is documented to make a font available
112
+ // to GDI; that DirectWrite's system collection also sees it is true on current
113
+ // Windows and is the sort of thing that is true until it is not. The test that
114
+ // would settle it is an app calling expo-font and then drawing in that family,
115
+ // which needs a host.
116
+
117
+ } // namespace basalt