Support percentage-and-pixel calc() values in SVG length attributes

This commit is contained in:
wuggy 2026-09-24 15:24:37 -07:00
commit 5e93ebd7f1
2 changed files with 115 additions and 13 deletions

View file

@ -115,6 +115,62 @@ GetValueFromString(const nsAString& aString,
return IsValidUnitType(*aUnitType);
}
// SVG geometry attributes can contain a percentage with a pixel adjustment.
// Keep the two terms separate so the percentage follows viewport changes.
static bool
GetPercentageCalcFromString(const nsAString& aString,
float& aPercentage, float& aPixelOffset)
{
nsAutoString value(aString);
value.Trim(" \t\r\n\f");
if (value.Length() < 6 ||
!Substring(value, 0, 5).LowerCaseEqualsLiteral("calc(") ||
value.Last() != ')') {
return false;
}
RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(value) + 5;
const RangedPtr<const char16_t> end =
SVGContentUtils::GetEndRangedPtr(value) - 1;
while (iter < end && IsSVGWhitespace(*iter)) {
++iter;
}
if (!SVGContentUtils::ParseNumber(iter, end, aPercentage) ||
iter == end || *iter++ != '%') {
return false;
}
if (iter == end || !IsSVGWhitespace(*iter)) {
return false;
}
while (iter < end && IsSVGWhitespace(*iter)) {
++iter;
}
if (iter == end || (*iter != '+' && *iter != '-')) {
return false;
}
bool subtract = *iter++ == '-';
if (iter == end || !IsSVGWhitespace(*iter)) {
return false;
}
while (iter < end && IsSVGWhitespace(*iter)) {
++iter;
}
if (!SVGContentUtils::ParseNumber(iter, end, aPixelOffset) ||
end - iter < 2 || *iter++ != 'p' || *iter++ != 'x') {
return false;
}
while (iter < end && IsSVGWhitespace(*iter)) {
++iter;
}
if (iter != end) {
return false;
}
if (subtract) {
aPixelOffset = -aPixelOffset;
}
return true;
}
static float GetMMPerPixel() { return MM_PER_INCH_FLOAT / 96; }
static float
@ -272,7 +328,7 @@ nsSVGLength2::SetBaseValueInSpecifiedUnits(float aValue,
nsSVGElement *aSVGElement,
bool aDoSetAttr)
{
if (mIsBaseSet && mBaseVal == aValue) {
if (mIsBaseSet && mBaseVal == aValue && !mHasBaseCalc) {
return;
}
@ -282,6 +338,8 @@ nsSVGLength2::SetBaseValueInSpecifiedUnits(float aValue,
}
mBaseVal = aValue;
mIsBaseSet = true;
mBaseCalcOffset = 0;
mHasBaseCalc = false;
if (!mIsAnimated) {
mAnimVal = mBaseVal;
}
@ -300,7 +358,8 @@ nsSVGLength2::ConvertToSpecifiedUnits(uint16_t unitType,
if (!IsValidUnitType(unitType))
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
if (mIsBaseSet && mSpecifiedUnitType == uint8_t(unitType))
if (mIsBaseSet && mSpecifiedUnitType == uint8_t(unitType) &&
!mHasBaseCalc)
return NS_OK;
// Even though we're not changing the visual effect this length will have
@ -309,9 +368,10 @@ nsSVGLength2::ConvertToSpecifiedUnits(uint16_t unitType,
// change.
nsAttrValue emptyOrOldValue = aSVGElement->WillChangeLength(mAttrEnum);
float valueInUserUnits =
mBaseVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType);
float valueInUserUnits = GetBaseValue(aSVGElement);
mSpecifiedUnitType = uint8_t(unitType);
mHasBaseCalc = false;
mBaseCalcOffset = 0;
// Setting aDoSetAttr to false here will ensure we don't call
// Will/DidChangeAngle a second time (and dispatch duplicate notifications).
SetBaseValue(valueInUserUnits, aSVGElement, false);
@ -331,7 +391,7 @@ nsSVGLength2::NewValueSpecifiedUnits(uint16_t unitType,
if (!IsValidUnitType(unitType))
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
if (mIsBaseSet && mBaseVal == valueInSpecifiedUnits &&
if (mIsBaseSet && mBaseVal == valueInSpecifiedUnits && !mHasBaseCalc &&
mSpecifiedUnitType == uint8_t(unitType)) {
return NS_OK;
}
@ -339,6 +399,8 @@ nsSVGLength2::NewValueSpecifiedUnits(uint16_t unitType,
nsAttrValue emptyOrOldValue = aSVGElement->WillChangeLength(mAttrEnum);
mBaseVal = valueInSpecifiedUnits;
mIsBaseSet = true;
mBaseCalcOffset = 0;
mHasBaseCalc = false;
mSpecifiedUnitType = uint8_t(unitType);
if (!mIsAnimated) {
mAnimVal = mBaseVal;
@ -379,13 +441,20 @@ nsSVGLength2::SetBaseValueString(const nsAString &aValueAsString,
{
float value;
uint16_t unitType;
float pixelOffset = 0;
bool isCalc = false;
if (!GetValueFromString(aValueAsString, value, &unitType)) {
return NS_ERROR_DOM_SYNTAX_ERR;
if (!GetPercentageCalcFromString(aValueAsString, value, pixelOffset)) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
unitType = nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE;
isCalc = true;
}
if (mIsBaseSet && mBaseVal == float(value) &&
mSpecifiedUnitType == uint8_t(unitType)) {
mSpecifiedUnitType == uint8_t(unitType) &&
mBaseCalcOffset == pixelOffset && mHasBaseCalc == isCalc) {
return NS_OK;
}
@ -396,6 +465,8 @@ nsSVGLength2::SetBaseValueString(const nsAString &aValueAsString,
mBaseVal = value;
mIsBaseSet = true;
mSpecifiedUnitType = uint8_t(unitType);
mBaseCalcOffset = pixelOffset;
mHasBaseCalc = isCalc;
if (!mIsAnimated) {
mAnimVal = mBaseVal;
}
@ -413,18 +484,36 @@ void
nsSVGLength2::GetBaseValueString(nsAString & aValueAsString) const
{
GetValueString(aValueAsString, mBaseVal, mSpecifiedUnitType);
if (mHasBaseCalc) {
nsAutoString percentage(aValueAsString);
nsAutoString pixels;
GetValueString(pixels, std::abs(mBaseCalcOffset),
nsIDOMSVGLength::SVG_LENGTHTYPE_PX);
aValueAsString.AssignLiteral("calc(");
aValueAsString.Append(percentage);
aValueAsString.Append(mBaseCalcOffset < 0 ? NS_LITERAL_STRING(" - ")
: NS_LITERAL_STRING(" + "));
aValueAsString.Append(pixels);
aValueAsString.Append(')');
}
}
void
nsSVGLength2::GetAnimValueString(nsAString & aValueAsString) const
{
GetValueString(aValueAsString, mAnimVal, mSpecifiedUnitType);
if (mHasBaseCalc && !mIsAnimated) {
GetBaseValueString(aValueAsString);
}
}
void
nsSVGLength2::SetBaseValue(float aValue, nsSVGElement *aSVGElement,
bool aDoSetAttr)
{
if (mHasBaseCalc) {
mSpecifiedUnitType = nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER;
}
SetBaseValueInSpecifiedUnits(aValue * GetUnitScaleFactor(aSVGElement,
mSpecifiedUnitType),
aSVGElement, aDoSetAttr);

View file

@ -98,6 +98,8 @@ public:
mCtxType = aCtxType;
mIsAnimated = false;
mIsBaseSet = false;
mBaseCalcOffset = 0;
mHasBaseCalc = false;
}
nsSVGLength2& operator=(const nsSVGLength2& aLength) {
@ -106,6 +108,8 @@ public:
mSpecifiedUnitType = aLength.mSpecifiedUnitType;
mIsAnimated = aLength.mIsAnimated;
mIsBaseSet = aLength.mIsBaseSet;
mBaseCalcOffset = aLength.mBaseCalcOffset;
mHasBaseCalc = aLength.mHasBaseCalc;
return *this;
}
@ -116,16 +120,21 @@ public:
void GetAnimValueString(nsAString& aValue) const;
float GetBaseValue(nsSVGElement* aSVGElement) const
{ return mBaseVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType); }
{ return mBaseVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType) +
mBaseCalcOffset; }
float GetAnimValue(nsSVGElement* aSVGElement) const
{ return mAnimVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType); }
{ return mAnimVal / GetUnitScaleFactor(aSVGElement, mSpecifiedUnitType) +
(mIsAnimated ? 0 : mBaseCalcOffset); }
float GetAnimValue(nsIFrame* aFrame) const
{ return mAnimVal / GetUnitScaleFactor(aFrame, mSpecifiedUnitType); }
{ return mAnimVal / GetUnitScaleFactor(aFrame, mSpecifiedUnitType) +
(mIsAnimated ? 0 : mBaseCalcOffset); }
float GetAnimValue(mozilla::dom::SVGSVGElement* aCtx) const
{ return mAnimVal / GetUnitScaleFactor(aCtx, mSpecifiedUnitType); }
{ return mAnimVal / GetUnitScaleFactor(aCtx, mSpecifiedUnitType) +
(mIsAnimated ? 0 : mBaseCalcOffset); }
float GetAnimValue(const UserSpaceMetrics& aMetrics) const
{ return mAnimVal / GetUnitScaleFactor(aMetrics, mSpecifiedUnitType); }
{ return mAnimVal / GetUnitScaleFactor(aMetrics, mSpecifiedUnitType) +
(mIsAnimated ? 0 : mBaseCalcOffset); }
uint8_t GetCtxType() const { return mCtxType; }
uint8_t GetSpecifiedUnitType() const { return mSpecifiedUnitType; }
@ -135,7 +144,8 @@ public:
float GetBaseValInSpecifiedUnits() const { return mBaseVal; }
float GetBaseValue(mozilla::dom::SVGSVGElement* aCtx) const
{ return mBaseVal / GetUnitScaleFactor(aCtx, mSpecifiedUnitType); }
{ return mBaseVal / GetUnitScaleFactor(aCtx, mSpecifiedUnitType) +
mBaseCalcOffset; }
bool HasBaseVal() const {
return mIsBaseSet;
@ -158,11 +168,14 @@ private:
float mAnimVal;
float mBaseVal;
// The px term of a calc(<percentage> +/- <px>) base value.
float mBaseCalcOffset;
uint8_t mSpecifiedUnitType;
uint8_t mAttrEnum; // element specified tracking for attribute
uint8_t mCtxType; // X, Y or Unspecified
bool mIsAnimated:1;
bool mIsBaseSet:1;
bool mHasBaseCalc:1;
float GetUnitScaleFactor(nsIFrame *aFrame, uint8_t aUnitType) const;
float GetUnitScaleFactor(const UserSpaceMetrics& aMetrics, uint8_t aUnitType) const;