Fix app-unit rounding for border width edges

This commit is contained in:
Basilisk-Dev 2026-05-09 18:30:47 -04:00 committed by wuggy
commit cf8b773656
2 changed files with 127 additions and 21 deletions

View file

@ -319,6 +319,11 @@ nsRuleNode::EnsureInlineDisplay(StyleDisplay& display)
}
}
enum class AppUnitRounding {
Default,
TowardZero
};
static nscoord CalcLengthWith(const nsCSSValue& aValue,
nscoord aFontSize,
const nsStyleFont* aStyleFont,
@ -326,7 +331,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
nsPresContext* aPresContext,
bool aUseProvidedRootEmSize,
bool aUseUserFontSet,
RuleNodeCacheConditions& aConditions);
RuleNodeCacheConditions& aConditions,
AppUnitRounding aRounding =
AppUnitRounding::Default);
struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
public css::NumbersAlreadyNormalizedOps
@ -339,18 +346,21 @@ struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
const bool mUseProvidedRootEmSize;
const bool mUseUserFontSet;
RuleNodeCacheConditions& mConditions;
const AppUnitRounding mRounding;
CalcLengthCalcOps(nscoord aFontSize, const nsStyleFont* aStyleFont,
nsStyleContext* aStyleContext, nsPresContext* aPresContext,
bool aUseProvidedRootEmSize, bool aUseUserFontSet,
RuleNodeCacheConditions& aConditions)
RuleNodeCacheConditions& aConditions,
AppUnitRounding aRounding)
: mFontSize(aFontSize),
mStyleFont(aStyleFont),
mStyleContext(aStyleContext),
mPresContext(aPresContext),
mUseProvidedRootEmSize(aUseProvidedRootEmSize),
mUseUserFontSet(aUseUserFontSet),
mConditions(aConditions)
mConditions(aConditions),
mRounding(aRounding)
{
}
@ -358,15 +368,84 @@ struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
{
return CalcLengthWith(aValue, mFontSize, mStyleFont,
mStyleContext, mPresContext, mUseProvidedRootEmSize,
mUseUserFontSet, mConditions);
mUseUserFontSet, mConditions, mRounding);
}
};
static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor)
static inline nscoord
ScaleCoordWithRounding(const nsCSSValue& aValue, float aFactor,
AppUnitRounding aRounding)
{
if (aRounding == AppUnitRounding::TowardZero) {
return NSToCoordTruncClamped(aValue.GetFloatValue() * aFactor);
}
return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor);
}
static inline nscoord
CSSPixelsToAppUnitsWithRounding(float aPixels, AppUnitRounding aRounding)
{
if (aRounding == AppUnitRounding::TowardZero) {
return NSToCoordTruncClamped(double(aPixels) *
nsPresContext::AppUnitsPerCSSPixel());
}
return nsPresContext::CSSPixelsToAppUnits(aPixels);
}
static nscoord
GetFixedLengthWithRounding(const nsCSSValue& aValue,
nsPresContext* aPresContext,
AppUnitRounding aRounding)
{
if (aRounding == AppUnitRounding::Default) {
return aValue.GetFixedLength(aPresContext);
}
MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_PhysicalMillimeter,
"not a fixed length unit");
double inches = double(aValue.GetFloatValue()) / MM_PER_INCH_FLOAT;
return NSToCoordTruncClamped(
inches * aPresContext->DeviceContext()->AppUnitsPerPhysicalInch());
}
static nscoord
GetPixelLengthWithRounding(const nsCSSValue& aValue,
AppUnitRounding aRounding)
{
MOZ_ASSERT(aValue.IsPixelLengthUnit(), "not a pixel length unit");
double scaleFactor;
switch (aValue.GetUnit()) {
case eCSSUnit_Pixel:
return CSSPixelsToAppUnitsWithRounding(aValue.GetFloatValue(),
aRounding);
case eCSSUnit_Pica:
scaleFactor = 16.0;
break;
case eCSSUnit_Point:
scaleFactor = 4.0 / 3.0;
break;
case eCSSUnit_Inch:
scaleFactor = 96.0;
break;
case eCSSUnit_Millimeter:
scaleFactor = 96.0 / 25.4;
break;
case eCSSUnit_Centimeter:
scaleFactor = 96.0 / 2.54;
break;
case eCSSUnit_Quarter:
scaleFactor = 96.0 / 101.6;
break;
default:
NS_ERROR("should never get here");
return 0;
}
return CSSPixelsToAppUnitsWithRounding(
float(double(aValue.GetFloatValue()) * scaleFactor), aRounding);
}
static inline nscoord ScaleViewportCoordTrunc(const nsCSSValue& aValue,
nscoord aViewportSize)
{
@ -465,7 +544,8 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
// except when called from
// CalcLengthWithInitialFont.
bool aUseUserFontSet,
RuleNodeCacheConditions& aConditions)
RuleNodeCacheConditions& aConditions,
AppUnitRounding aRounding)
{
NS_ASSERTION(aValue.IsLengthUnit() || aValue.IsCalcUnit(),
"not a length or calc unit");
@ -476,10 +556,10 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
NS_ASSERTION(aPresContext, "Must have prescontext");
if (aValue.IsFixedLengthUnit()) {
return aValue.GetFixedLength(aPresContext);
return GetFixedLengthWithRounding(aValue, aPresContext, aRounding);
}
if (aValue.IsPixelLengthUnit()) {
return aValue.GetPixelLength();
return GetPixelLengthWithRounding(aValue, aRounding);
}
if (aValue.IsCalcUnit()) {
// For properties for which lengths are the *only* units accepted in
@ -490,7 +570,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
CalcLengthCalcOps ops(aFontSize, aStyleFont,
aStyleContext, aPresContext,
aUseProvidedRootEmSize, aUseUserFontSet,
aConditions);
aConditions, aRounding);
return css::ComputeCalc(aValue, ops);
}
switch (aValue.GetUnit()) {
@ -623,7 +703,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
rootFontSize = rootStyleFont->mFont.size;
}
return ScaleCoordRound(aValue, float(rootFontSize));
return ScaleCoordWithRounding(aValue, float(rootFontSize), aRounding);
}
default:
// Fall through to the code for units that can't be stored in the
@ -648,7 +728,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
// CSS2.1 specifies that this unit scales to the computed font
// size, not the em-width in the font metrics, despite the name.
aConditions.SetFontSizeDependency(aFontSize);
return ScaleCoordRound(aValue, float(aFontSize));
return ScaleCoordWithRounding(aValue, float(aFontSize), aRounding);
}
case eCSSUnit_XHeight: {
aPresContext->SetUsesExChUnits(true);
@ -656,7 +736,7 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
GetMetricsFor(aPresContext, aStyleContext, styleFont,
aFontSize, aUseUserFontSet);
aConditions.SetUncacheable();
return ScaleCoordRound(aValue, float(fm->XHeight()));
return ScaleCoordWithRounding(aValue, float(fm->XHeight()), aRounding);
}
case eCSSUnit_Char: {
aPresContext->SetUsesExChUnits(true);
@ -668,8 +748,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
GetMetrics(fm->Orientation()).zeroOrAveCharWidth;
aConditions.SetUncacheable();
return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() *
zeroWidth));
return ScaleCoordWithRounding(
aValue, ceil(aPresContext->AppUnitsPerDevPixel() * zeroWidth),
aRounding);
}
default:
NS_NOTREACHED("unexpected unit");
@ -701,6 +782,20 @@ static inline nscoord CalcLength(const nsCSSValue& aValue,
aPresContext, aConditions);
}
static inline nscoord
CalcLengthTowardZero(const nsCSSValue& aValue,
nsStyleContext* aStyleContext,
nsPresContext* aPresContext,
RuleNodeCacheConditions& aConditions)
{
NS_ASSERTION(aStyleContext, "Must have style data");
return CalcLengthWith(aValue, -1, nullptr,
aStyleContext, aPresContext,
false, true, aConditions,
AppUnitRounding::TowardZero);
}
/* static */ nscoord
nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext,
const nsCSSValue& aValue)
@ -8048,13 +8143,11 @@ nsRuleNode::ComputeBorderData(void* aStartStruct,
"Unexpected enum value");
border->SetBorderWidth(side,
(mPresContext->GetBorderWidthTable())[value.GetIntValue()]);
// OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
} else if (SetCoord(value, coord, nsStyleCoord(),
SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
aContext, mPresContext, conditions)) {
NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
} else if (value.IsLengthUnit() || value.IsCalcUnit()) {
// clamp negative calc() to 0.
border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0));
border->SetBorderWidth(side,
std::max(CalcLengthTowardZero(value, aContext, mPresContext,
conditions), 0));
} else if (eCSSUnit_Inherit == value.GetUnit()) {
conditions.SetUncacheable();
border->SetBorderWidth(side,
@ -8336,6 +8429,11 @@ nsRuleNode::ComputeOutlineData(void* aStartStruct,
eCSSUnit_Revert == outlineWidthValue->GetUnit()) {
outline->mOutlineWidth =
nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated);
} else if (outlineWidthValue->IsLengthUnit() ||
outlineWidthValue->IsCalcUnit()) {
outline->mOutlineWidth.SetCoordValue(
CalcLengthTowardZero(*outlineWidthValue, aContext, mPresContext,
conditions));
} else {
SetCoord(*outlineWidthValue, outline->mOutlineWidth,
parentOutline->mOutlineWidth,
@ -8346,7 +8444,13 @@ nsRuleNode::ComputeOutlineData(void* aStartStruct,
// outline-offset: length, inherit
nsStyleCoord tempCoord;
const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset();
if (SetCoord(*outlineOffsetValue, tempCoord,
if (outlineOffsetValue->IsLengthUnit() || outlineOffsetValue->IsCalcUnit()) {
outline->mOutlineOffset =
NS_ROUND_OFFSET_TO_PIXELS(
CalcLengthTowardZero(*outlineOffsetValue, aContext, mPresContext,
conditions),
mPresContext->AppUnitsPerDevPixel());
} else if (SetCoord(*outlineOffsetValue, tempCoord,
nsStyleCoord(parentOutline->mOutlineOffset,
nsStyleCoord::CoordConstructor),
SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |

View file

@ -24,6 +24,8 @@ var tests = [
["2px", "2px"],
["2.75px", "2px"],
["2.999px", "2px"],
["3px", "3px"],
["3.001px", "3px"],
];
function checkWidth(property, setupProperty, setupValue) {