diff --git a/Sources/YogaKit/YGLayout.mm b/Sources/YogaKit/YGLayout.mm index ff665f9f..38dc11f7 100644 --- a/Sources/YogaKit/YGLayout.mm +++ b/Sources/YogaKit/YGLayout.mm @@ -116,12 +116,12 @@ - (void)set##objc_capitalized_name:(YGValue)objc_lowercased_name YGValue YGPointValue(CGFloat value) { - return (YGValue) { .value = (YGUnit) value, .unit = (YGUnit) YGUnitPoint }; + return (YGValue) { .value = (float) value, .unit = (YGUnit) YGUnitPoint }; } YGValue YGPercentValue(CGFloat value) { - return (YGValue) { .value = (YGUnit) value, .unit = YGUnitPercent }; + return (YGValue) { .value = (float) value, .unit = YGUnitPercent }; } static YGConfigRef globalConfig; @@ -337,8 +337,8 @@ static YGSize YGMeasureView( }]; return (YGSize) { - .width = (YGUnit) YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), - .height = (YGUnit) YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), + .width = (float) YGSanitizeMeasurement(constrainedWidth, sizeThatFits.width, widthMode), + .height = (float) YGSanitizeMeasurement(constrainedHeight, sizeThatFits.height, heightMode), }; } diff --git a/Sources/yoga/Yoga.cpp b/Sources/yoga/Yoga.cpp index ff57e99f..f9f0318e 100644 --- a/Sources/yoga/Yoga.cpp +++ b/Sources/yoga/Yoga.cpp @@ -788,11 +788,17 @@ bool YGLayoutNodeInternal(const YGNodeRef node, const char *reason, const YGConfigRef config); -bool YGFloatsEqual(const float a, const float b) { +bool YGFloatsEqualWithPrecision(const float a, const float b, const float precision) { + assert(precision > 0); + if (YGFloatIsUndefined(a)) { return YGFloatIsUndefined(b); } - return fabs(a - b) < 0.0001f; + return fabs(a - b) < precision; +} + +bool YGFloatsEqual(const float a, const float b) { + return YGFloatsEqualWithPrecision(a, b, 0.0001f); } static void YGNodePrintInternal(const YGNodeRef node, @@ -3142,12 +3148,13 @@ float YGRoundValueToPixelGrid(const float value, const float pointScaleFactor, const bool forceCeil, const bool forceFloor) { + const float roundingError = fmax(0.0001, 0.01 * pointScaleFactor); float scaledValue = value * pointScaleFactor; float fractial = fmodf(scaledValue, 1.0); - if (YGFloatsEqual(fractial, 0)) { + if (YGFloatsEqualWithPrecision(fractial, 0.0, roundingError)) { // First we check if the value is already rounded scaledValue = scaledValue - fractial; - } else if (YGFloatsEqual(fractial, 1.0)) { + } else if (YGFloatsEqualWithPrecision(fractial, 1.0, roundingError)) { scaledValue = scaledValue - fractial + 1.0; } else if (forceCeil) { // Next we check if we need to use forced rounding @@ -3157,7 +3164,7 @@ float YGRoundValueToPixelGrid(const float value, } else { // Finally we just round the value scaledValue = scaledValue - fractial + - (fractial > 0.5f || YGFloatsEqual(fractial, 0.5f) ? 1.0f : 0.0f); + (fractial > 0.5f || YGFloatsEqualWithPrecision(fractial, 0.5f, roundingError) ? 1.0f : 0.0f); } return scaledValue / pointScaleFactor; } diff --git a/core-tests/YGPixelGridRounding.cpp b/core-tests/YGPixelGridRounding.cpp new file mode 100644 index 00000000..b38dc64b --- /dev/null +++ b/core-tests/YGPixelGridRounding.cpp @@ -0,0 +1,57 @@ +#include +#include + +// This test scrutinize next behaviours: +// - pixel grid snapping in 1e4..0 coordinate range +// - ability to layout nodes with smallest possible dimensions (one pixel separators) +// - providing text node layout with bounds strictly larger than sized + +TEST(YogaTest, pixel_grid_rounding_table) { + const float kPointScale = 3; + + const YGConfigRef config = YGConfigNew(); + YGConfigSetPointScaleFactor(config, kPointScale); + + const float kSeparatorHeight = 1 / kPointScale; + const float kCellContentHeight = 44.5; + const int kCellsCount = 100; + + const YGNodeRef root = YGNodeNewWithConfig(config); + + int subnodesCount = 0; + + for (int i = 0; i < kCellsCount; i++) { + const YGNodeRef separator = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(separator, kSeparatorHeight); + YGNodeInsertChild(root, separator, subnodesCount++); + + const YGNodeRef cell = YGNodeNewWithConfig(config); + YGNodeSetNodeType(cell, YGNodeTypeText); + YGNodeStyleSetHeight(cell, kCellContentHeight); + YGNodeInsertChild(root, cell, subnodesCount++); + } + + const YGNodeRef separator = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(separator, kSeparatorHeight); + YGNodeInsertChild(root, separator, subnodesCount++); + + YGNodeCalculateLayout(root, 375, YGUndefined, YGDirectionLTR); + + EXPECT_LE(kCellsCount * (kSeparatorHeight + kCellContentHeight) + kSeparatorHeight, YGNodeLayoutGetHeight(root)); + EXPECT_FLOAT_EQ(375, YGNodeLayoutGetWidth(root)); + + for (int i = 0; i < YGNodeGetChildCount(root); i++) { + const YGNodeRef child = YGNodeGetChild(root, i); + const float childHeight = YGNodeLayoutGetHeight(child); + + if (YGNodeGetNodeType(child) == YGNodeTypeText) { + EXPECT_GT(childHeight, kCellContentHeight); + } else { + EXPECT_GT(childHeight, 0); + } + } + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} diff --git a/core-tests/YGRoundingFunctionTest.cpp b/core-tests/YGRoundingFunctionTest.cpp index 82eba44d..0b8a3f4f 100644 --- a/core-tests/YGRoundingFunctionTest.cpp +++ b/core-tests/YGRoundingFunctionTest.cpp @@ -19,10 +19,15 @@ TEST(YogaTest, rounding_value) { ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.999999, 2.0, false, true)); // Test that numbers with fraction are rounded correctly accounting for ceil/floor flags - ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.01, 2.0, false, false)); - ASSERT_FLOAT_EQ(6.5, YGRoundValueToPixelGrid(6.01, 2.0, true, false)); - ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.01, 2.0, false, true)); - ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.99, 2.0, false, false)); - ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.99, 2.0, true, false)); - ASSERT_FLOAT_EQ(5.5, YGRoundValueToPixelGrid(5.99, 2.0, false, true)); + ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.1, 2.0, false, false)); + ASSERT_FLOAT_EQ(6.5, YGRoundValueToPixelGrid(6.1, 2.0, true, false)); + ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(6.1, 2.0, false, true)); + ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.9, 2.0, false, false)); + ASSERT_FLOAT_EQ(6.0, YGRoundValueToPixelGrid(5.9, 2.0, true, false)); + ASSERT_FLOAT_EQ(5.5, YGRoundValueToPixelGrid(5.9, 2.0, false, true)); + + // Are we able to treat value as rounded for reasonably large number? + ASSERT_FLOAT_EQ(527.6666666, YGRoundValueToPixelGrid(527.666, 3.0, false, true)); + ASSERT_FLOAT_EQ(527.6666666, YGRoundValueToPixelGrid(527.666, 3.0, true, false)); + ASSERT_FLOAT_EQ(527.6666666, YGRoundValueToPixelGrid(527.666, 3.0, true, true)); }