diff --git a/tests/YGRoundingLargeOffsetTest.cpp b/tests/YGRoundingLargeOffsetTest.cpp new file mode 100644 index 0000000000..b8378449c1 --- /dev/null +++ b/tests/YGRoundingLargeOffsetTest.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +static YGSize _measureTwoLines( + YGNodeConstRef /*node*/, + float /*width*/, + YGMeasureMode /*widthMode*/, + float /*height*/, + YGMeasureMode /*heightMode*/) { + return YGSize{292.0f, 44.0f}; +} + +// A measured (text) node far from the origin on a 3x screen, with its top on +// one side of a power of two and its bottom on the other. Each edge is rounded +// to the pixel grid and the height is their difference; in float the two +// edges have different precision and the height came out one step short (for +// example 43.9998779 instead of 44 for a node spanning 2048), so a text +// renderer given that box dropped the last line. +TEST(YogaTest, rounding_measured_height_is_exact_across_float_steps) { + for (const float spacer : + {1012.0f + 1.0f / 3.0f, + 1012.0f + 2.0f / 3.0f, + 2004.0f + 1.0f / 3.0f, + 2028.0f + 2.0f / 3.0f, + 4052.0f + 2.0f / 3.0f}) { + YGConfigRef config = YGConfigNew(); + YGConfigSetPointScaleFactor(config, 3.0f); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 300); + + YGNodeRef spacerNode = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(spacerNode, spacer); + YGNodeInsertChild(root, spacerNode, 0); + + YGNodeRef text = YGNodeNewWithConfig(config); + YGNodeSetMeasureFunc(text, _measureTwoLines); + YGNodeInsertChild(root, text, 1); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + + ASSERT_EQ(44.0f, YGNodeLayoutGetHeight(text)) << "spacer " << spacer; + + YGNodeFreeRecursive(root); + YGConfigFree(config); + } +} diff --git a/yoga/algorithm/PixelGrid.cpp b/yoga/algorithm/PixelGrid.cpp index 754a10b3a1..d92f381ad7 100644 --- a/yoga/algorithm/PixelGrid.cpp +++ b/yoga/algorithm/PixelGrid.cpp @@ -12,7 +12,14 @@ namespace facebook::yoga { -float roundValueToPixelGrid( +namespace { + +// Rounds in double precision. A dimension is the difference of two rounded +// edges, and far from the origin a float has fewer bits than a pixel: taking +// that difference in float can leave it a step short of the real size (for +// example 43.9998 instead of 44 for a node spanning y = 2048 on a 3x screen), +// which makes a text renderer drop the last line. Subtract first, then narrow. +double roundValueToPixelGridDouble( const double value, const double pointScaleFactor, const bool forceCeil, @@ -60,7 +67,18 @@ float roundValueToPixelGrid( } return (std::isnan(scaledValue) || std::isnan(pointScaleFactor)) ? YGUndefined - : (float)(scaledValue / pointScaleFactor); + : scaledValue / pointScaleFactor; +} + +} // namespace + +float roundValueToPixelGrid( + const double value, + const double pointScaleFactor, + const bool forceCeil, + const bool forceFloor) { + return static_cast(roundValueToPixelGridDouble( + value, pointScaleFactor, forceCeil, forceFloor)); } void roundLayoutResultsToPixelGrid( @@ -109,23 +127,25 @@ void roundLayoutResultsToPixelGrid( node->getLayout().setDimension( Dimension::Width, - roundValueToPixelGrid( - absoluteNodeRight, - pointScaleFactor, - (textRounding && hasFractionalWidth), - (textRounding && !hasFractionalWidth)) - - roundValueToPixelGrid( - absoluteNodeLeft, pointScaleFactor, false, textRounding)); + static_cast( + roundValueToPixelGridDouble( + absoluteNodeRight, + pointScaleFactor, + (textRounding && hasFractionalWidth), + (textRounding && !hasFractionalWidth)) - + roundValueToPixelGridDouble( + absoluteNodeLeft, pointScaleFactor, false, textRounding))); node->getLayout().setDimension( Dimension::Height, - roundValueToPixelGrid( - absoluteNodeBottom, - pointScaleFactor, - (textRounding && hasFractionalHeight), - (textRounding && !hasFractionalHeight)) - - roundValueToPixelGrid( - absoluteNodeTop, pointScaleFactor, false, textRounding)); + static_cast( + roundValueToPixelGridDouble( + absoluteNodeBottom, + pointScaleFactor, + (textRounding && hasFractionalHeight), + (textRounding && !hasFractionalHeight)) - + roundValueToPixelGridDouble( + absoluteNodeTop, pointScaleFactor, false, textRounding))); } for (yoga::Node* child : node->getChildren()) {