Skip to content

Commit d866b67

Browse files
authored
Merge pull request #63 from zintus/pixel-grid-rounding
Rounding behaviour
2 parents 3677ce1 + 03bf7f8 commit d866b67

File tree

4 files changed

+84
-15
lines changed

4 files changed

+84
-15
lines changed

Sources/YogaKit/YGLayout.mm

+4-4
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,12 @@ - (void)set##objc_capitalized_name:(YGValue)objc_lowercased_name
116116

117117
YGValue YGPointValue(CGFloat value)
118118
{
119-
return (YGValue) { .value = (YGUnit) value, .unit = (YGUnit) YGUnitPoint };
119+
return (YGValue) { .value = (float) value, .unit = (YGUnit) YGUnitPoint };
120120
}
121121

122122
YGValue YGPercentValue(CGFloat value)
123123
{
124-
return (YGValue) { .value = (YGUnit) value, .unit = YGUnitPercent };
124+
return (YGValue) { .value = (float) value, .unit = YGUnitPercent };
125125
}
126126

127127
static YGConfigRef globalConfig;
@@ -337,8 +337,8 @@ static YGSize YGMeasureView(
337337
}];
338338

339339
return (YGSize) {
340-
.width = (YGUnit) YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode),
341-
.height = (YGUnit) YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode),
340+
.width = (float) YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode),
341+
.height = (float) YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode),
342342
};
343343
}
344344

Sources/yoga/Yoga.cpp

+12-5
Original file line numberDiff line numberDiff line change
@@ -788,11 +788,17 @@ bool YGLayoutNodeInternal(const YGNodeRef node,
788788
const char *reason,
789789
const YGConfigRef config);
790790

791-
bool YGFloatsEqual(const float a, const float b) {
791+
bool YGFloatsEqualWithPrecision(const float a, const float b, const float precision) {
792+
assert(precision > 0);
793+
792794
if (YGFloatIsUndefined(a)) {
793795
return YGFloatIsUndefined(b);
794796
}
795-
return fabs(a - b) < 0.0001f;
797+
return fabs(a - b) < precision;
798+
}
799+
800+
bool YGFloatsEqual(const float a, const float b) {
801+
return YGFloatsEqualWithPrecision(a, b, 0.0001f);
796802
}
797803

798804
static void YGNodePrintInternal(const YGNodeRef node,
@@ -3142,12 +3148,13 @@ float YGRoundValueToPixelGrid(const float value,
31423148
const float pointScaleFactor,
31433149
const bool forceCeil,
31443150
const bool forceFloor) {
3151+
const float roundingError = fmax(0.0001, 0.01 * pointScaleFactor);
31453152
float scaledValue = value * pointScaleFactor;
31463153
float fractial = fmodf(scaledValue, 1.0);
3147-
if (YGFloatsEqual(fractial, 0)) {
3154+
if (YGFloatsEqualWithPrecision(fractial, 0.0, roundingError)) {
31483155
// First we check if the value is already rounded
31493156
scaledValue = scaledValue - fractial;
3150-
} else if (YGFloatsEqual(fractial, 1.0)) {
3157+
} else if (YGFloatsEqualWithPrecision(fractial, 1.0, roundingError)) {
31513158
scaledValue = scaledValue - fractial + 1.0;
31523159
} else if (forceCeil) {
31533160
// Next we check if we need to use forced rounding
@@ -3157,7 +3164,7 @@ float YGRoundValueToPixelGrid(const float value,
31573164
} else {
31583165
// Finally we just round the value
31593166
scaledValue = scaledValue - fractial +
3160-
(fractial > 0.5f || YGFloatsEqual(fractial, 0.5f) ? 1.0f : 0.0f);
3167+
(fractial > 0.5f || YGFloatsEqualWithPrecision(fractial, 0.5f, roundingError) ? 1.0f : 0.0f);
31613168
}
31623169
return scaledValue / pointScaleFactor;
31633170
}

core-tests/YGPixelGridRounding.cpp

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <gtest/gtest.h>
2+
#include <yoga/Yoga.h>
3+
4+
// This test scrutinize next behaviours:
5+
// - pixel grid snapping in 1e4..0 coordinate range
6+
// - ability to layout nodes with smallest possible dimensions (one pixel separators)
7+
// - providing text node layout with bounds strictly larger than sized
8+
9+
TEST(YogaTest, pixel_grid_rounding_table) {
10+
const float kPointScale = 3;
11+
12+
const YGConfigRef config = YGConfigNew();
13+
YGConfigSetPointScaleFactor(config, kPointScale);
14+
15+
const float kSeparatorHeight = 1 / kPointScale;
16+
const float kCellContentHeight = 44.5;
17+
const int kCellsCount = 100;
18+
19+
const YGNodeRef root = YGNodeNewWithConfig(config);
20+
21+
int subnodesCount = 0;
22+
23+
for (int i = 0; i < kCellsCount; i++) {
24+
const YGNodeRef separator = YGNodeNewWithConfig(config);
25+
YGNodeStyleSetHeight(separator, kSeparatorHeight);
26+
YGNodeInsertChild(root, separator, subnodesCount++);
27+
28+
const YGNodeRef cell = YGNodeNewWithConfig(config);
29+
YGNodeSetNodeType(cell, YGNodeTypeText);
30+
YGNodeStyleSetHeight(cell, kCellContentHeight);
31+
YGNodeInsertChild(root, cell, subnodesCount++);
32+
}
33+
34+
const YGNodeRef separator = YGNodeNewWithConfig(config);
35+
YGNodeStyleSetHeight(separator, kSeparatorHeight);
36+
YGNodeInsertChild(root, separator, subnodesCount++);
37+
38+
YGNodeCalculateLayout(root, 375, YGUndefined, YGDirectionLTR);
39+
40+
EXPECT_LE(kCellsCount * (kSeparatorHeight + kCellContentHeight) + kSeparatorHeight, YGNodeLayoutGetHeight(root));
41+
EXPECT_FLOAT_EQ(375, YGNodeLayoutGetWidth(root));
42+
43+
for (int i = 0; i < YGNodeGetChildCount(root); i++) {
44+
const YGNodeRef child = YGNodeGetChild(root, i);
45+
const float childHeight = YGNodeLayoutGetHeight(child);
46+
47+
if (YGNodeGetNodeType(child) == YGNodeTypeText) {
48+
EXPECT_GT(childHeight, kCellContentHeight);
49+
} else {
50+
EXPECT_GT(childHeight, 0);
51+
}
52+
}
53+
54+
YGNodeFreeRecursive(root);
55+
56+
YGConfigFree(config);
57+
}

core-tests/YGRoundingFunctionTest.cpp

+11-6
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ TEST(YogaTest, rounding_value) {
1919
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.999999, 2.0, false, true));
2020

2121
// Test that numbers with fraction are rounded correctly accounting for ceil/floor flags
22-
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.01, 2.0, false, false));
23-
ASSERT_FLOAT_EQ(6.5, YGRoundValueToPixelGrid(6.01, 2.0, true, false));
24-
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.01, 2.0, false, true));
25-
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.99, 2.0, false, false));
26-
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.99, 2.0, true, false));
27-
ASSERT_FLOAT_EQ(5.5, YGRoundValueToPixelGrid(5.99, 2.0, false, true));
22+
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.1, 2.0, false, false));
23+
ASSERT_FLOAT_EQ(6.5, YGRoundValueToPixelGrid(6.1, 2.0, true, false));
24+
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.1, 2.0, false, true));
25+
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.9, 2.0, false, false));
26+
ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.9, 2.0, true, false));
27+
ASSERT_FLOAT_EQ(5.5, YGRoundValueToPixelGrid(5.9, 2.0, false, true));
28+
29+
// Are we able to treat value as rounded for reasonably large number?
30+
ASSERT_FLOAT_EQ(527.6666666, YGRoundValueToPixelGrid(527.666, 3.0, false, true));
31+
ASSERT_FLOAT_EQ(527.6666666, YGRoundValueToPixelGrid(527.666, 3.0, true, false));
32+
ASSERT_FLOAT_EQ(527.6666666, YGRoundValueToPixelGrid(527.666, 3.0, true, true));
2833
}

0 commit comments

Comments
 (0)