basalt-core 0.0.0-rc5 → 0.2.0
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/dist/cli/basalt.d.ts.map +1 -1
- package/dist/cli/basalt.js +18 -1
- package/dist/cli/basalt.js.map +1 -1
- package/dist/cli/bundle-cli.js +0 -0
- package/dist/cli/desktop.d.ts.map +1 -1
- package/dist/cli/desktop.js +53 -4
- package/dist/cli/desktop.js.map +1 -1
- package/dist/cli/packageApp.d.ts.map +1 -1
- package/dist/cli/packageApp.js +7 -1
- package/dist/cli/packageApp.js.map +1 -1
- package/dist/metro-config.d.ts.map +1 -1
- package/dist/metro-config.js +53 -5
- package/dist/metro-config.js.map +1 -1
- package/dist/src/KeyHandler.d.ts +1 -1
- package/dist/src/KeyHandler.d.ts.map +1 -1
- package/dist/src/KeyHandler.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/overrides/Alert.d.ts +1 -1
- package/dist/src/overrides/Alert.js +1 -1
- package/dist/src/overrides/BaseViewConfig.d.ts +10 -3
- package/dist/src/overrides/BaseViewConfig.d.ts.map +1 -1
- package/dist/src/overrides/BaseViewConfig.js +21 -3
- package/dist/src/overrides/BaseViewConfig.js.map +1 -1
- package/dist/src/overrides/Button.d.ts +81 -0
- package/dist/src/overrides/Button.d.ts.map +1 -0
- package/dist/src/overrides/Button.js +109 -0
- package/dist/src/overrides/Button.js.map +1 -0
- package/dist/src/overrides/ScrollView.d.ts +25 -2
- package/dist/src/overrides/ScrollView.d.ts.map +1 -1
- package/dist/src/overrides/ScrollView.js +64 -7
- package/dist/src/overrides/ScrollView.js.map +1 -1
- package/dist/src/overrides/Settings.d.ts +81 -0
- package/dist/src/overrides/Settings.d.ts.map +1 -0
- package/dist/src/overrides/Settings.js +120 -0
- package/dist/src/overrides/Settings.js.map +1 -0
- package/dist/src/overrides/processDecelerationRate.d.ts +20 -0
- package/dist/src/overrides/processDecelerationRate.d.ts.map +1 -0
- package/dist/src/overrides/processDecelerationRate.js +31 -0
- package/dist/src/overrides/processDecelerationRate.js.map +1 -0
- package/dist/src/useContextMenu.d.ts +57 -6
- package/dist/src/useContextMenu.d.ts.map +1 -1
- package/dist/src/useContextMenu.js +34 -1
- package/dist/src/useContextMenu.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/native/CMakeLists.txt +85 -1
- package/native/cmake/Skia.cmake +29 -6
- package/native/core/ActionSheet.cpp +75 -0
- package/native/core/ActionSheet.h +90 -0
- package/native/core/AppStateHost.cpp +76 -0
- package/native/core/AppStateHost.h +68 -0
- package/native/core/BackgroundLayers.h +282 -0
- package/native/core/BlendModes.h +67 -0
- package/native/core/CoreModules.cpp +380 -11
- package/native/core/CoreModules.h +140 -4
- package/native/core/CrashHandler.cpp +246 -0
- package/native/core/CrashHandler.h +65 -0
- package/native/core/CursorNames.h +110 -0
- package/native/core/DevMenu.cpp +6 -5
- package/native/core/DeviceMetrics.cpp +87 -0
- package/native/core/DeviceMetrics.h +80 -0
- package/native/core/Filters.h +336 -0
- package/native/core/FontFitting.h +183 -0
- package/native/core/FontScaling.h +153 -0
- package/native/core/FontVariants.h +148 -0
- package/native/core/Gradients.h +523 -0
- package/native/core/ImageAnimation.h +109 -0
- package/native/core/ImageBytes.cpp +28 -1
- package/native/core/ImageBytes.h +17 -1
- package/native/core/ImageProgress.h +85 -0
- package/native/core/LabelRegistry.h +189 -0
- package/native/core/LegacyShadow.h +90 -0
- package/native/core/LiveRegions.h +114 -0
- package/native/core/MenuModule.cpp +35 -4
- package/native/core/MountingWalk.h +125 -2
- package/native/core/PlatformServices.h +187 -3
- package/native/core/SafeAreaComponent.cpp +115 -0
- package/native/core/SafeAreaComponent.h +105 -0
- package/native/core/ScreensComponent.cpp +201 -0
- package/native/core/ScreensComponent.h +216 -0
- package/native/core/ScrollBounds.cpp +8 -0
- package/native/core/ScrollBounds.h +14 -0
- package/native/core/ScrollIndicator.h +67 -0
- package/native/core/ScrollMomentum.cpp +24 -0
- package/native/core/ScrollMomentum.h +14 -0
- package/native/core/ScrollSnap.cpp +121 -12
- package/native/core/ScrollSnap.h +45 -0
- package/native/core/ScrollVisiblePosition.cpp +68 -0
- package/native/core/ScrollVisiblePosition.h +101 -0
- package/native/core/SettingsStore.cpp +199 -0
- package/native/core/SettingsStore.h +127 -0
- package/native/core/ShadowShape.h +38 -0
- package/native/core/SkiaViewComponent.cpp +11 -0
- package/native/core/SkiaViewComponent.h +50 -0
- package/native/core/SystemLocale.cpp +103 -0
- package/native/core/SystemLocale.h +86 -0
- package/native/core/TestDialog.cpp +45 -6
- package/native/core/TestDialog.h +15 -0
- package/native/core/TextAlignments.h +240 -0
- package/native/core/TextChecking.h +133 -0
- package/native/core/TextColors.h +83 -0
- package/native/core/TextDecorations.h +101 -0
- package/native/core/TextDirection.cpp +242 -0
- package/native/core/TextDirection.h +88 -0
- package/native/core/TextHighlight.h +30 -0
- package/native/core/TextSelection.cpp +97 -0
- package/native/core/TextSelection.h +161 -0
- package/native/core/TextShadows.h +96 -0
- package/native/core/TextVerticalAlign.h +33 -0
- package/native/core/UIManagerAccess.cpp +36 -0
- package/native/core/UIManagerAccess.h +28 -0
- package/native/core/WindowBoundsCache.cpp +58 -1
- package/native/core/WindowControl.h +24 -0
- package/native/core/WritingDirections.h +52 -0
- package/native/core/portability_probe.cpp +7 -0
- package/native/tests/RenamedFontFile.h +161 -0
- package/native/tests/TestHarness.cpp +18 -0
- package/native/tests/test_action_sheet.cpp +157 -0
- package/native/tests/test_app_state.cpp +60 -0
- package/native/tests/test_background_layers.cpp +281 -0
- package/native/tests/test_blend_modes.cpp +79 -0
- package/native/tests/test_blobs.cpp +1 -1
- package/native/tests/test_device_metrics.cpp +126 -0
- package/native/tests/test_filters.cpp +271 -0
- package/native/tests/test_font_fitting.cpp +172 -0
- package/native/tests/test_font_scaling.cpp +127 -0
- package/native/tests/test_font_variants.cpp +84 -0
- package/native/tests/test_gradients.cpp +390 -0
- package/native/tests/test_image_animation.cpp +105 -0
- package/native/tests/test_image_progress.cpp +94 -0
- package/native/tests/test_label_registry.cpp +262 -0
- package/native/tests/test_legacy_shadow.cpp +165 -0
- package/native/tests/test_live_regions.cpp +108 -0
- package/native/tests/test_menu_entries.cpp +192 -0
- package/native/tests/test_momentum.cpp +45 -0
- package/native/tests/test_scroll_bounds.cpp +42 -0
- package/native/tests/test_scroll_indicator.cpp +61 -0
- package/native/tests/test_scroll_snap.cpp +129 -0
- package/native/tests/test_scroll_visible_position.cpp +121 -0
- package/native/tests/test_settings_store.cpp +257 -0
- package/native/tests/test_shadow_shape.cpp +47 -0
- package/native/tests/test_system_locale.cpp +36 -0
- package/native/tests/test_text_alignment.cpp +299 -0
- package/native/tests/test_text_checking.cpp +31 -0
- package/native/tests/test_text_colors.cpp +92 -0
- package/native/tests/test_text_decorations.cpp +100 -0
- package/native/tests/test_text_selection.cpp +168 -0
- package/native/tests/test_text_shadows.cpp +132 -0
- package/package.json +4 -2
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// Which edge a paragraph's text sits against, and which way it runs.
|
|
2
|
+
//
|
|
3
|
+
// The two halves of one question that three hosts were each answering locally
|
|
4
|
+
// and differently; `core/TextAlignments.h` holds the measurements that prompted
|
|
5
|
+
// this. What is asserted here is the table itself, plus Unicode's rule P2 over
|
|
6
|
+
// UTF-8 -- the direction a paragraph runs when the app said `natural` or said
|
|
7
|
+
// nothing, which is the common case and the one every relative alignment needs.
|
|
8
|
+
//
|
|
9
|
+
// The GTK suite holds `textStartsRightToLeft` against `pango_find_base_dir`
|
|
10
|
+
// over the same scripts, so the rule is checked against a real implementation
|
|
11
|
+
// of it rather than only against this file's expectations.
|
|
12
|
+
|
|
13
|
+
#include "TestHarness.h"
|
|
14
|
+
|
|
15
|
+
#include "TextAlignments.h"
|
|
16
|
+
#include "TextDirection.h"
|
|
17
|
+
|
|
18
|
+
#include <sstream>
|
|
19
|
+
#include <string>
|
|
20
|
+
|
|
21
|
+
using basalt::PhysicalTextAlignment;
|
|
22
|
+
using basalt::physicalTextAlignment;
|
|
23
|
+
using basalt::textStartsRightToLeft;
|
|
24
|
+
using facebook::react::TextAlignment;
|
|
25
|
+
|
|
26
|
+
namespace {
|
|
27
|
+
|
|
28
|
+
// UTF-8 as a source file can carry it without depending on the compiler's idea
|
|
29
|
+
// of an execution charset.
|
|
30
|
+
const std::string kHebrew = "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d"; // Hebrew
|
|
31
|
+
const std::string kArabic = "\xd8\xa7\xd9\x84\xd8\xb3\xd9\x84\xd8\xa7\xd9\x85"; // Arabic
|
|
32
|
+
const std::string kThaana = "\xde\x8b\xde\xa8"; // Thaana
|
|
33
|
+
const std::string kNko = "\xdf\x92\xdf\x8a"; // NKo
|
|
34
|
+
|
|
35
|
+
const char *name(PhysicalTextAlignment alignment) {
|
|
36
|
+
switch (alignment) {
|
|
37
|
+
case PhysicalTextAlignment::Left:
|
|
38
|
+
return "left";
|
|
39
|
+
case PhysicalTextAlignment::Center:
|
|
40
|
+
return "center";
|
|
41
|
+
case PhysicalTextAlignment::Right:
|
|
42
|
+
return "right";
|
|
43
|
+
case PhysicalTextAlignment::Justified:
|
|
44
|
+
return "justified";
|
|
45
|
+
}
|
|
46
|
+
return "?";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The common case: a paragraph whose text and whose app run the same way.
|
|
50
|
+
std::string edge(std::optional<TextAlignment> alignment, bool rightToLeft) {
|
|
51
|
+
return name(physicalTextAlignment(
|
|
52
|
+
alignment, basalt::TextDirections{.paragraph = rightToLeft, .relative = rightToLeft}));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#if BASALT_RN_MINOR >= 87
|
|
56
|
+
// The two apart: `text` is which way this paragraph's own text runs, `layout`
|
|
57
|
+
// is which way the app is laid out. They differ whenever an app shows Latin
|
|
58
|
+
// text on a mirrored desktop, or Hebrew in a left-to-right one.
|
|
59
|
+
//
|
|
60
|
+
// Behind the same guard as its callers: `start` and `end` arrived in 0.87, so
|
|
61
|
+
// on 0.86 every test that uses this is compiled out and an unused function is
|
|
62
|
+
// an error here.
|
|
63
|
+
std::string edgeIn(std::optional<TextAlignment> alignment, bool text, bool layout) {
|
|
64
|
+
return name(physicalTextAlignment(
|
|
65
|
+
alignment, basalt::TextDirections{.paragraph = text, .relative = layout}));
|
|
66
|
+
}
|
|
67
|
+
#endif
|
|
68
|
+
|
|
69
|
+
} // namespace
|
|
70
|
+
|
|
71
|
+
TEST(alignment_the_physical_two_are_the_same_in_both_directions) {
|
|
72
|
+
// `left` means the left of the box, in any paragraph. This is the row AppKit
|
|
73
|
+
// had wrong: it folded `left` into `natural`, so a right-to-left paragraph
|
|
74
|
+
// with `textAlign: 'left'` drew flush right.
|
|
75
|
+
EXPECT_EQ(edge(TextAlignment::Left, false), std::string("left"));
|
|
76
|
+
EXPECT_EQ(edge(TextAlignment::Left, true), std::string("left"));
|
|
77
|
+
EXPECT_EQ(edge(TextAlignment::Right, false), std::string("right"));
|
|
78
|
+
EXPECT_EQ(edge(TextAlignment::Right, true), std::string("right"));
|
|
79
|
+
EXPECT_EQ(edge(TextAlignment::Center, false), std::string("center"));
|
|
80
|
+
EXPECT_EQ(edge(TextAlignment::Center, true), std::string("center"));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
TEST(alignment_natural_follows_the_direction) {
|
|
84
|
+
// React Native's default, and the answer for a paragraph that said nothing.
|
|
85
|
+
EXPECT_EQ(edge(TextAlignment::Natural, false), std::string("left"));
|
|
86
|
+
EXPECT_EQ(edge(TextAlignment::Natural, true), std::string("right"));
|
|
87
|
+
EXPECT_EQ(edge(std::nullopt, false), std::string("left"));
|
|
88
|
+
EXPECT_EQ(edge(std::nullopt, true), std::string("right"));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#if BASALT_RN_MINOR >= 87
|
|
92
|
+
TEST(alignment_start_and_end_are_the_edges_the_line_runs_between) {
|
|
93
|
+
// The entry this file closes: `end` is where a line finishes, which in a
|
|
94
|
+
// right-to-left paragraph is the left edge. All three hosts drew it flush
|
|
95
|
+
// right, two of them by folding it into `right`.
|
|
96
|
+
EXPECT_EQ(edge(TextAlignment::Start, false), std::string("left"));
|
|
97
|
+
EXPECT_EQ(edge(TextAlignment::Start, true), std::string("right"));
|
|
98
|
+
EXPECT_EQ(edge(TextAlignment::End, false), std::string("right"));
|
|
99
|
+
EXPECT_EQ(edge(TextAlignment::End, true), std::string("left"));
|
|
100
|
+
}
|
|
101
|
+
#endif
|
|
102
|
+
|
|
103
|
+
#if BASALT_RN_MINOR >= 87
|
|
104
|
+
TEST(alignment_start_and_end_follow_the_layout_rather_than_the_text) {
|
|
105
|
+
// The distinction this pair exists for, and backlog/text.md entry 14.
|
|
106
|
+
//
|
|
107
|
+
// `start` is the word every other relative thing in React Native uses --
|
|
108
|
+
// `paddingStart`, `marginEnd`, `borderStartColor` -- and Yoga resolves all
|
|
109
|
+
// of those against the layout direction. A paragraph that resolved it
|
|
110
|
+
// against its own text would face the other way from its own padding on a
|
|
111
|
+
// mirrored desktop showing Latin text, which is the case here.
|
|
112
|
+
//
|
|
113
|
+
// Upstream resolves it the same way: `RCTResolveTextAlignment` in
|
|
114
|
+
// `RCTAttributedTextUtils.mm` takes the layout direction and answers `Start`
|
|
115
|
+
// from it. The field it reads is `TextAttributes::layoutDirection`, filled
|
|
116
|
+
// by `ParagraphShadowNode::getContent` from Yoga.
|
|
117
|
+
EXPECT_EQ(edgeIn(TextAlignment::Start, /*text=*/false, /*layout=*/true), std::string("right"));
|
|
118
|
+
EXPECT_EQ(edgeIn(TextAlignment::End, /*text=*/false, /*layout=*/true), std::string("left"));
|
|
119
|
+
// And the other way round: Hebrew in an app that is not mirrored.
|
|
120
|
+
EXPECT_EQ(edgeIn(TextAlignment::Start, /*text=*/true, /*layout=*/false), std::string("left"));
|
|
121
|
+
EXPECT_EQ(edgeIn(TextAlignment::End, /*text=*/true, /*layout=*/false), std::string("right"));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
TEST(alignment_natural_follows_the_text_rather_than_the_layout) {
|
|
125
|
+
// The other half of the same decision, and deliberately not symmetrical:
|
|
126
|
+
// `natural` is React Native's default and means "the edge this text starts
|
|
127
|
+
// from", so Hebrew in a left-to-right app is still flush right. Upstream's
|
|
128
|
+
// iOS half agrees -- `Natural` is the one value `RCTResolveTextAlignment`
|
|
129
|
+
// passes through untouched, for `NSParagraphStyle` to resolve from the
|
|
130
|
+
// paragraph's own writing direction.
|
|
131
|
+
EXPECT_EQ(edgeIn(TextAlignment::Natural, /*text=*/true, /*layout=*/false), std::string("right"));
|
|
132
|
+
EXPECT_EQ(edgeIn(std::nullopt, /*text=*/true, /*layout=*/false), std::string("right"));
|
|
133
|
+
EXPECT_EQ(edgeIn(TextAlignment::Natural, /*text=*/false, /*layout=*/true), std::string("left"));
|
|
134
|
+
EXPECT_EQ(edgeIn(std::nullopt, /*text=*/false, /*layout=*/true), std::string("left"));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
TEST(alignment_a_paragraph_that_says_which_way_it_runs_decides_its_own_edges) {
|
|
138
|
+
// `textDirections` rather than the table: the question is which direction
|
|
139
|
+
// reaches `relative`, and the answer is the prop when there is one.
|
|
140
|
+
using facebook::react::WritingDirection;
|
|
141
|
+
const std::string latin = "hello";
|
|
142
|
+
// Said nothing: the layout decides, which is upstream's rule.
|
|
143
|
+
EXPECT(basalt::textDirections(std::nullopt, latin, true).relative);
|
|
144
|
+
EXPECT(!basalt::textDirections(std::nullopt, latin, false).relative);
|
|
145
|
+
EXPECT(basalt::textDirections(WritingDirection::Natural, latin, true).relative);
|
|
146
|
+
// Said so: the prop decides, in an app mirrored either way. `end` is the
|
|
147
|
+
// edge this paragraph's lines finish at, which is what it just declared.
|
|
148
|
+
EXPECT(basalt::textDirections(WritingDirection::RightToLeft, latin, false).relative);
|
|
149
|
+
EXPECT(!basalt::textDirections(WritingDirection::LeftToRight, latin, true).relative);
|
|
150
|
+
// And the text alone never decides it, which is the change: Hebrew in an app
|
|
151
|
+
// that is not mirrored has `start` on the left.
|
|
152
|
+
EXPECT(!basalt::textDirections(std::nullopt, kHebrew, false).relative);
|
|
153
|
+
EXPECT(basalt::textDirections(std::nullopt, kHebrew, false).paragraph);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
TEST(alignment_left_and_right_stay_physical_in_a_mirrored_app) {
|
|
157
|
+
// Upstream's iOS half mirrors these two with the layout, so `textAlign:
|
|
158
|
+
// 'left'` draws flush right in a right-to-left app. This platform does not,
|
|
159
|
+
// and backlog/text.md records the divergence with the call that would make
|
|
160
|
+
// it upstream's. `left` is the left of the box on all three hosts, which is
|
|
161
|
+
// what the rest of this file is written to.
|
|
162
|
+
EXPECT_EQ(edgeIn(TextAlignment::Left, /*text=*/false, /*layout=*/true), std::string("left"));
|
|
163
|
+
EXPECT_EQ(edgeIn(TextAlignment::Right, /*text=*/false, /*layout=*/true), std::string("right"));
|
|
164
|
+
}
|
|
165
|
+
#endif
|
|
166
|
+
|
|
167
|
+
TEST(alignment_justified_is_not_an_edge_and_stays_itself) {
|
|
168
|
+
// It reaches the engine, which stretches the lines; the flush factor below is
|
|
169
|
+
// for the last line, which goes against the start edge.
|
|
170
|
+
EXPECT_EQ(edge(TextAlignment::Justified, false), std::string("justified"));
|
|
171
|
+
EXPECT_EQ(edge(TextAlignment::Justified, true), std::string("justified"));
|
|
172
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Justified, false), 0.0, 0.001);
|
|
173
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Justified, true), 1.0, 0.001);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
TEST(alignment_flush_factors_are_the_three_core_text_takes) {
|
|
177
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Left, false), 0.0, 0.001);
|
|
178
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Center, false), 0.5, 0.001);
|
|
179
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Right, false), 1.0, 0.001);
|
|
180
|
+
// The direction does not enter into it: the edge has already been resolved.
|
|
181
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Left, true), 0.0, 0.001);
|
|
182
|
+
EXPECT_NEAR(basalt::textFlushFactor(PhysicalTextAlignment::Right, true), 1.0, 0.001);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
TEST(direction_a_right_to_left_script_makes_a_right_to_left_paragraph) {
|
|
186
|
+
EXPECT(textStartsRightToLeft(kHebrew));
|
|
187
|
+
EXPECT(textStartsRightToLeft(kArabic));
|
|
188
|
+
EXPECT(textStartsRightToLeft(kThaana));
|
|
189
|
+
EXPECT(textStartsRightToLeft(kNko));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
TEST(direction_latin_and_every_other_left_to_right_script_does_not) {
|
|
193
|
+
EXPECT(!textStartsRightToLeft("hello"));
|
|
194
|
+
// Latin with diacritics, Japanese, Greek and Cyrillic, as UTF-8 bytes: this
|
|
195
|
+
// file is compiled by MSVC too, which reads a source as the machine's code
|
|
196
|
+
// page unless it is told otherwise, and would re-encode a literal written in
|
|
197
|
+
// the script itself. The escapes say exactly what reaches the function.
|
|
198
|
+
// Split before the `e`: a hex escape in C++ is greedy, so "\x9fe" is one
|
|
199
|
+
// escape and out of range rather than an eszett followed by a letter.
|
|
200
|
+
EXPECT(!textStartsRightToLeft("Gr\xc3\xbc\xc3\x9f" "e")); // Gruesse
|
|
201
|
+
EXPECT(!textStartsRightToLeft("\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e")); // Japanese
|
|
202
|
+
EXPECT(!textStartsRightToLeft("\xce\x95\xce\xbb\xce\xbb\xce\xb7")); // Greek
|
|
203
|
+
EXPECT(!textStartsRightToLeft("\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2")); // Cyrillic
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
TEST(direction_the_first_strong_character_decides_and_the_rest_do_not) {
|
|
207
|
+
// Rule P2: the *first* strong character, not the majority and not the last.
|
|
208
|
+
EXPECT(!textStartsRightToLeft("hello " + kHebrew));
|
|
209
|
+
EXPECT(textStartsRightToLeft(kHebrew + " hello"));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
TEST(direction_punctuation_digits_and_spaces_are_skipped) {
|
|
213
|
+
// What sits in front of a word in real text. A quotation mark deciding the
|
|
214
|
+
// direction of the sentence after it is the bug this skipping prevents.
|
|
215
|
+
EXPECT(textStartsRightToLeft(" \"" + kHebrew + "\""));
|
|
216
|
+
EXPECT(textStartsRightToLeft("(1) " + kHebrew));
|
|
217
|
+
EXPECT(!textStartsRightToLeft("(1) hello"));
|
|
218
|
+
EXPECT(!textStartsRightToLeft("123"));
|
|
219
|
+
EXPECT(!textStartsRightToLeft(""));
|
|
220
|
+
EXPECT(!textStartsRightToLeft(" "));
|
|
221
|
+
// An emoji is not a letter either.
|
|
222
|
+
EXPECT(textStartsRightToLeft("\xf0\x9f\x98\x80 " + kHebrew));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
TEST(direction_arabic_indic_digits_are_not_strong) {
|
|
226
|
+
// U+0660..0669 are class AN, inside the Arabic block and not strong, so a
|
|
227
|
+
// paragraph that starts with them is not right-to-left by P2. Reading the
|
|
228
|
+
// block as a whole is the obvious mistake here.
|
|
229
|
+
const std::string digits = "\xd9\xa1\xd9\xa2\xd9\xa3"; // Arabic-Indic 123
|
|
230
|
+
EXPECT(!textStartsRightToLeft(digits));
|
|
231
|
+
EXPECT(!textStartsRightToLeft(digits + " hello"));
|
|
232
|
+
EXPECT(textStartsRightToLeft(digits + " " + kArabic));
|
|
233
|
+
// The extended Arabic-Indic digits, U+06F0..06F9, which are class EN.
|
|
234
|
+
const std::string extended = "\xdb\xb1\xdb\xb2"; // extended Arabic-Indic 12
|
|
235
|
+
EXPECT(!textStartsRightToLeft(extended + " hello"));
|
|
236
|
+
EXPECT(textStartsRightToLeft(extended + " " + kArabic));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
TEST(direction_a_right_to_left_mark_is_strong_on_its_own) {
|
|
240
|
+
// U+200F is class R and is how a string with no letters at all can still ask
|
|
241
|
+
// for a right-to-left paragraph.
|
|
242
|
+
EXPECT(textStartsRightToLeft("\xe2\x80\x8f 123"));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
TEST(direction_broken_utf8_is_skipped_rather_than_refused) {
|
|
246
|
+
// This decides an alignment. A string with a bad byte in it still has to lay
|
|
247
|
+
// out, and refusing would mean a paragraph that does not draw.
|
|
248
|
+
EXPECT(!textStartsRightToLeft("\xff\xfe"));
|
|
249
|
+
EXPECT(textStartsRightToLeft("\xff" + kHebrew));
|
|
250
|
+
// A truncated sequence at the end, which is what a cut-off string looks like.
|
|
251
|
+
EXPECT(!textStartsRightToLeft("\xd7"));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
TEST(direction_the_prop_wins_when_the_app_set_one) {
|
|
255
|
+
using facebook::react::WritingDirection;
|
|
256
|
+
EXPECT(!basalt::paragraphIsRightToLeft(WritingDirection::LeftToRight, kHebrew));
|
|
257
|
+
EXPECT(basalt::paragraphIsRightToLeft(WritingDirection::RightToLeft, "hello"));
|
|
258
|
+
// `natural` and nothing are the same question, and both ask the text.
|
|
259
|
+
EXPECT(basalt::paragraphIsRightToLeft(WritingDirection::Natural, kHebrew));
|
|
260
|
+
EXPECT(basalt::paragraphIsRightToLeft(std::nullopt, kHebrew));
|
|
261
|
+
EXPECT(!basalt::paragraphIsRightToLeft(std::nullopt, "hello"));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// --- The other axis -----------------------------------------------------------
|
|
265
|
+
//
|
|
266
|
+
// `textAlignVertical`, which puts the paragraph somewhere in its own box, and
|
|
267
|
+
// which `verticalAlign` becomes in React Native's own JavaScript before any of
|
|
268
|
+
// this sees it. The arithmetic is three lines and one of them is the clamp,
|
|
269
|
+
// which is the line that matters: a paragraph taller than its box must start at
|
|
270
|
+
// the top rather than above it.
|
|
271
|
+
|
|
272
|
+
TEST(vertical_alignment_flush_factors_are_the_three_positions) {
|
|
273
|
+
using V = facebook::react::TextAlignmentVertical;
|
|
274
|
+
EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Top), 0.0, 0.001);
|
|
275
|
+
EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Center), 0.5, 0.001);
|
|
276
|
+
EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Bottom), 1.0, 0.001);
|
|
277
|
+
// `auto`, and saying nothing, are both the top: that is what every engine
|
|
278
|
+
// does with no instruction.
|
|
279
|
+
EXPECT_NEAR(basalt::textVerticalFlushFactor(V::Auto), 0.0, 0.001);
|
|
280
|
+
EXPECT_NEAR(basalt::textVerticalFlushFactor(std::nullopt), 0.0, 0.001);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
TEST(vertical_alignment_offsets_the_slack_in_the_box) {
|
|
284
|
+
// A 40pt paragraph in a 100pt box: 60 points of slack.
|
|
285
|
+
EXPECT_NEAR(basalt::textVerticalOffset(100.0, 40.0, 0.0), 0.0, 0.001);
|
|
286
|
+
EXPECT_NEAR(basalt::textVerticalOffset(100.0, 40.0, 0.5), 30.0, 0.001);
|
|
287
|
+
EXPECT_NEAR(basalt::textVerticalOffset(100.0, 40.0, 1.0), 60.0, 0.001);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
TEST(vertical_alignment_a_paragraph_taller_than_its_box_starts_at_the_top) {
|
|
291
|
+
// The `numberOfLines` case: the box is the lines that fit and the text is all
|
|
292
|
+
// of them. A negative offset would push the first line out through the top
|
|
293
|
+
// edge, where nothing can scroll it back.
|
|
294
|
+
EXPECT_NEAR(basalt::textVerticalOffset(40.0, 100.0, 1.0), 0.0, 0.001);
|
|
295
|
+
EXPECT_NEAR(basalt::textVerticalOffset(40.0, 100.0, 0.5), 0.0, 0.001);
|
|
296
|
+
// And exactly as tall is no slack at all, which is every paragraph Yoga sized
|
|
297
|
+
// to its own content.
|
|
298
|
+
EXPECT_NEAR(basalt::textVerticalOffset(40.0, 40.0, 1.0), 0.0, 0.001);
|
|
299
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// `spellCheck` and `autoCorrect` as three states. See core/TextChecking.h.
|
|
2
|
+
//
|
|
3
|
+
// One rule, and it is the whole reason this is shared: unset is not false. A
|
|
4
|
+
// field that says nothing wants the platform's default, and resolving that to
|
|
5
|
+
// `false` would opt every ordinary field out of spell checking without the app
|
|
6
|
+
// ever asking.
|
|
7
|
+
|
|
8
|
+
#include "TestHarness.h"
|
|
9
|
+
|
|
10
|
+
#include "TextChecking.h"
|
|
11
|
+
|
|
12
|
+
#include <sstream>
|
|
13
|
+
#include <string>
|
|
14
|
+
|
|
15
|
+
using basalt::TextCheckingFlag;
|
|
16
|
+
using basalt::textCheckingFlag;
|
|
17
|
+
using basalt::textCheckingName;
|
|
18
|
+
|
|
19
|
+
TEST(text_checking_unset_is_its_own_answer) {
|
|
20
|
+
EXPECT(textCheckingFlag(std::nullopt) == TextCheckingFlag::Unset);
|
|
21
|
+
EXPECT(textCheckingFlag(true) == TextCheckingFlag::On);
|
|
22
|
+
EXPECT(textCheckingFlag(false) == TextCheckingFlag::Off);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The dump says nothing for a field that asked for nothing, rather than
|
|
26
|
+
// printing the platform's default as though the app had chosen it.
|
|
27
|
+
TEST(text_checking_an_unset_flag_has_no_name) {
|
|
28
|
+
EXPECT(textCheckingName(TextCheckingFlag::Unset) == nullptr);
|
|
29
|
+
EXPECT_EQ(std::string(textCheckingName(TextCheckingFlag::On)), std::string("on"));
|
|
30
|
+
EXPECT_EQ(std::string(textCheckingName(TextCheckingFlag::Off)), std::string("off"));
|
|
31
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// A fragment's colours, and the `opacity` that multiplies them. See
|
|
2
|
+
// core/TextColors.h.
|
|
3
|
+
//
|
|
4
|
+
// Shared because the three rules are React Native's rather than a toolkit's:
|
|
5
|
+
// the default is opaque black and not the platform's label colour, opacity
|
|
6
|
+
// multiplies an alpha rather than replacing it, and a background nobody set is
|
|
7
|
+
// nothing rather than transparent black.
|
|
8
|
+
|
|
9
|
+
#include "TestHarness.h"
|
|
10
|
+
|
|
11
|
+
#include "TextColors.h"
|
|
12
|
+
|
|
13
|
+
#include <cmath>
|
|
14
|
+
|
|
15
|
+
using basalt::textBackgroundColor;
|
|
16
|
+
using basalt::textForegroundColor;
|
|
17
|
+
using basalt::textOpacity;
|
|
18
|
+
using facebook::react::ColorComponents;
|
|
19
|
+
using facebook::react::TextAttributes;
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
// Not `near`: see test_font_scaling.cpp for the Windows macro that takes.
|
|
24
|
+
bool closeTo(float a, float b) { return std::fabs(a - b) < 0.01F; }
|
|
25
|
+
|
|
26
|
+
TextAttributes coloured(float red, float alpha) {
|
|
27
|
+
TextAttributes attributes;
|
|
28
|
+
attributes.foregroundColor =
|
|
29
|
+
facebook::react::colorFromComponents(ColorComponents{red, 0.0F, 0.0F, alpha});
|
|
30
|
+
return attributes;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
} // namespace
|
|
34
|
+
|
|
35
|
+
// React Native's default, which is not the platform's.
|
|
36
|
+
TEST(text_colors_the_default_foreground_is_opaque_black) {
|
|
37
|
+
TextAttributes attributes;
|
|
38
|
+
const auto color = textForegroundColor(attributes);
|
|
39
|
+
EXPECT(closeTo(color.red, 0.0F));
|
|
40
|
+
EXPECT(closeTo(color.green, 0.0F));
|
|
41
|
+
EXPECT(closeTo(color.blue, 0.0F));
|
|
42
|
+
EXPECT(closeTo(color.alpha, 1.0F));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
TEST(text_colors_opacity_multiplies_the_alpha_rather_than_replacing_it) {
|
|
46
|
+
TextAttributes attributes = coloured(1.0F, 0.5F);
|
|
47
|
+
attributes.opacity = 0.5;
|
|
48
|
+
// Half of a half, within a byte: a colour is packed to eight bits a channel
|
|
49
|
+
// on the way in.
|
|
50
|
+
EXPECT(closeTo(textForegroundColor(attributes).alpha, 0.25F));
|
|
51
|
+
|
|
52
|
+
// And it applies to the default black too, which an app setting opacity and
|
|
53
|
+
// no colour depends on.
|
|
54
|
+
TextAttributes bare;
|
|
55
|
+
bare.opacity = 0.25;
|
|
56
|
+
EXPECT(closeTo(textForegroundColor(bare).alpha, 0.25F));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
TEST(text_colors_an_unset_opacity_changes_nothing) {
|
|
60
|
+
TextAttributes attributes = coloured(1.0F, 0.75F);
|
|
61
|
+
EXPECT(std::isnan(attributes.opacity));
|
|
62
|
+
EXPECT(closeTo(textForegroundColor(attributes).alpha, 0.75F));
|
|
63
|
+
EXPECT(closeTo(textOpacity(attributes), 1.0F));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A stylesheet can ask for either, and neither is a thing an alpha can be.
|
|
67
|
+
TEST(text_colors_an_opacity_outside_zero_to_one_is_clamped) {
|
|
68
|
+
TextAttributes high = coloured(1.0F, 1.0F);
|
|
69
|
+
high.opacity = 4.0;
|
|
70
|
+
EXPECT(closeTo(textForegroundColor(high).alpha, 1.0F));
|
|
71
|
+
|
|
72
|
+
TextAttributes low = coloured(1.0F, 1.0F);
|
|
73
|
+
low.opacity = -2.0;
|
|
74
|
+
EXPECT(closeTo(textForegroundColor(low).alpha, 0.0F));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Nothing, rather than transparent black: a host that drew it would paint a box
|
|
78
|
+
// behind text that asked for none.
|
|
79
|
+
TEST(text_colors_a_background_nobody_set_is_nothing) {
|
|
80
|
+
TextAttributes attributes;
|
|
81
|
+
EXPECT(!textBackgroundColor(attributes).has_value());
|
|
82
|
+
|
|
83
|
+
attributes.opacity = 0.5;
|
|
84
|
+
EXPECT(!textBackgroundColor(attributes).has_value());
|
|
85
|
+
|
|
86
|
+
attributes.backgroundColor =
|
|
87
|
+
facebook::react::colorFromComponents(ColorComponents{0.0F, 0.0F, 1.0F, 1.0F});
|
|
88
|
+
const auto background = textBackgroundColor(attributes);
|
|
89
|
+
EXPECT(background.has_value());
|
|
90
|
+
EXPECT(background.has_value() && closeTo(background->blue, 1.0F));
|
|
91
|
+
EXPECT(background.has_value() && closeTo(background->alpha, 0.5F));
|
|
92
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Which lines a fragment decorates with, and in what colour. See
|
|
2
|
+
// core/TextDecorations.h.
|
|
3
|
+
//
|
|
4
|
+
// Shared because the rules are decisions rather than drawing: a colour with no
|
|
5
|
+
// line draws nothing, an unset colour means the text's own, and the style is
|
|
6
|
+
// passed through for each toolkit to map to what it has.
|
|
7
|
+
|
|
8
|
+
#include "TestHarness.h"
|
|
9
|
+
|
|
10
|
+
#include "TextDecorations.h"
|
|
11
|
+
|
|
12
|
+
#include <cmath>
|
|
13
|
+
|
|
14
|
+
using basalt::textDecoration;
|
|
15
|
+
using facebook::react::ColorComponents;
|
|
16
|
+
using facebook::react::TextAttributes;
|
|
17
|
+
using facebook::react::TextDecorationLineType;
|
|
18
|
+
using facebook::react::TextDecorationStyle;
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
// Not `near`: see test_font_scaling.cpp for the Windows macro that takes.
|
|
23
|
+
bool closeTo(float a, float b) { return std::fabs(a - b) < 0.01F; }
|
|
24
|
+
|
|
25
|
+
} // namespace
|
|
26
|
+
|
|
27
|
+
TEST(text_decoration_underline_strikethrough_and_both) {
|
|
28
|
+
TextAttributes attributes;
|
|
29
|
+
attributes.textDecorationLineType = TextDecorationLineType::Underline;
|
|
30
|
+
auto decoration = textDecoration(attributes);
|
|
31
|
+
EXPECT(decoration.has_value());
|
|
32
|
+
EXPECT(decoration.has_value() && decoration->underline);
|
|
33
|
+
EXPECT(decoration.has_value() && !decoration->strikethrough);
|
|
34
|
+
|
|
35
|
+
attributes.textDecorationLineType = TextDecorationLineType::Strikethrough;
|
|
36
|
+
decoration = textDecoration(attributes);
|
|
37
|
+
EXPECT(decoration.has_value() && !decoration->underline);
|
|
38
|
+
EXPECT(decoration.has_value() && decoration->strikethrough);
|
|
39
|
+
|
|
40
|
+
attributes.textDecorationLineType = TextDecorationLineType::UnderlineStrikethrough;
|
|
41
|
+
decoration = textDecoration(attributes);
|
|
42
|
+
EXPECT(decoration.has_value() && decoration->underline);
|
|
43
|
+
EXPECT(decoration.has_value() && decoration->strikethrough);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The prop that decides whether there is a decoration at all.
|
|
47
|
+
TEST(text_decoration_needs_a_line_to_decorate) {
|
|
48
|
+
TextAttributes none;
|
|
49
|
+
EXPECT(!textDecoration(none).has_value());
|
|
50
|
+
|
|
51
|
+
none.textDecorationLineType = TextDecorationLineType::None;
|
|
52
|
+
EXPECT(!textDecoration(none).has_value());
|
|
53
|
+
|
|
54
|
+
// A colour and a style with no line is a stylesheet styling something it did
|
|
55
|
+
// not ask for, and drawing a line there would be inventing one.
|
|
56
|
+
TextAttributes styledOnly;
|
|
57
|
+
styledOnly.textDecorationStyle = TextDecorationStyle::Dotted;
|
|
58
|
+
styledOnly.textDecorationColor =
|
|
59
|
+
facebook::react::colorFromComponents(ColorComponents{1.0F, 0.0F, 0.0F, 1.0F});
|
|
60
|
+
EXPECT(!textDecoration(styledOnly).has_value());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
TEST(text_decoration_takes_the_colour_it_was_given) {
|
|
64
|
+
TextAttributes attributes;
|
|
65
|
+
attributes.textDecorationLineType = TextDecorationLineType::Underline;
|
|
66
|
+
attributes.textDecorationColor =
|
|
67
|
+
facebook::react::colorFromComponents(ColorComponents{0.0F, 0.5F, 1.0F, 1.0F});
|
|
68
|
+
const auto decoration = textDecoration(attributes);
|
|
69
|
+
EXPECT(decoration.has_value() && decoration->hasColor);
|
|
70
|
+
EXPECT(decoration.has_value() && closeTo(decoration->blue, 1.0F));
|
|
71
|
+
EXPECT(decoration.has_value() && closeTo(decoration->red, 0.0F));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Unset is not black: it is the text's own colour, which is what both toolkits
|
|
75
|
+
// draw when nothing names one.
|
|
76
|
+
TEST(text_decoration_an_unset_colour_is_the_texts_own) {
|
|
77
|
+
TextAttributes attributes;
|
|
78
|
+
attributes.textDecorationLineType = TextDecorationLineType::Underline;
|
|
79
|
+
const auto decoration = textDecoration(attributes);
|
|
80
|
+
EXPECT(decoration.has_value() && !decoration->hasColor);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
TEST(text_decoration_carries_the_style_for_each_host_to_map) {
|
|
84
|
+
TextAttributes attributes;
|
|
85
|
+
attributes.textDecorationLineType = TextDecorationLineType::Underline;
|
|
86
|
+
// Unset is solid, which is CSS's default and React Native's.
|
|
87
|
+
EXPECT(textDecoration(attributes)->style == TextDecorationStyle::Solid);
|
|
88
|
+
|
|
89
|
+
// `wavy` is 0.87's; 0.86's enum stops at Dashed, and naming the member
|
|
90
|
+
// there does not compile. The same guard the hosts carry.
|
|
91
|
+
for (const auto style : {TextDecorationStyle::Solid, TextDecorationStyle::Double,
|
|
92
|
+
TextDecorationStyle::Dotted, TextDecorationStyle::Dashed,
|
|
93
|
+
#if BASALT_RN_MINOR >= 87
|
|
94
|
+
TextDecorationStyle::Wavy,
|
|
95
|
+
#endif
|
|
96
|
+
TextDecorationStyle::Solid}) {
|
|
97
|
+
attributes.textDecorationStyle = style;
|
|
98
|
+
EXPECT(textDecoration(attributes)->style == style);
|
|
99
|
+
}
|
|
100
|
+
}
|