Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rounding behaviour #63

Merged
merged 5 commits into from
Feb 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Sources/YogaKit/YGLayout.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
};
}

Expand Down
17 changes: 12 additions & 5 deletions Sources/yoga/Yoga.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
57 changes: 57 additions & 0 deletions core-tests/YGPixelGridRounding.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <gtest/gtest.h>
#include <yoga/Yoga.h>

// 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);
}
17 changes: 11 additions & 6 deletions core-tests/YGRoundingFunctionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}