SVGLength.cpp (11202B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SVGLength.h" 8 9 #include <algorithm> 10 #include <limits> 11 12 #include "SVGContentUtils.h" 13 #include "mozilla/dom/SVGElement.h" 14 #include "nsCSSValue.h" 15 #include "nsTextFormatter.h" 16 17 using namespace mozilla::dom; 18 using namespace mozilla::dom::SVGLength_Binding; 19 20 namespace mozilla { 21 22 // These types are numbered so that different length categories are in 23 // contiguous ranges - See `SVGLength::Is[..]Unit()`. 24 const unsigned short SVG_LENGTHTYPE_Q = 11; 25 const unsigned short SVG_LENGTHTYPE_CH = 12; 26 const unsigned short SVG_LENGTHTYPE_REM = 13; 27 const unsigned short SVG_LENGTHTYPE_IC = 14; 28 const unsigned short SVG_LENGTHTYPE_CAP = 15; 29 const unsigned short SVG_LENGTHTYPE_LH = 16; 30 const unsigned short SVG_LENGTHTYPE_RLH = 17; 31 const unsigned short SVG_LENGTHTYPE_REX = 18; 32 const unsigned short SVG_LENGTHTYPE_RCH = 19; 33 const unsigned short SVG_LENGTHTYPE_RIC = 20; 34 const unsigned short SVG_LENGTHTYPE_RCAP = 21; 35 const unsigned short SVG_LENGTHTYPE_VW = 22; 36 const unsigned short SVG_LENGTHTYPE_VH = 23; 37 const unsigned short SVG_LENGTHTYPE_VMIN = 24; 38 const unsigned short SVG_LENGTHTYPE_VMAX = 25; 39 40 void SVGLength::GetValueAsString(nsAString& aValue) const { 41 nsTextFormatter::ssprintf(aValue, u"%g", (double)mValue); 42 43 nsAutoString unitString; 44 GetUnitString(unitString, mUnit); 45 aValue.Append(unitString); 46 } 47 48 bool SVGLength::SetValueFromString(const nsAString& aString) { 49 bool success; 50 auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); 51 52 if (!success) { 53 return false; 54 } 55 56 nsAString::const_iterator iter, end; 57 aString.BeginReading(iter); 58 aString.EndReading(end); 59 60 float value; 61 62 if (!SVGContentUtils::ParseNumber(iter, end, value)) { 63 return false; 64 } 65 66 const nsAString& units = Substring(iter, end); 67 uint16_t unitType = GetUnitTypeForString(units); 68 if (unitType == SVG_LENGTHTYPE_UNKNOWN) { 69 return false; 70 } 71 mValue = value; 72 mUnit = uint8_t(unitType); 73 return true; 74 } 75 76 /*static*/ 77 bool SVGLength::IsAbsoluteUnit(uint8_t aUnit) { 78 return aUnit == SVG_LENGTHTYPE_NUMBER || 79 (aUnit >= SVG_LENGTHTYPE_PX && aUnit <= SVG_LENGTHTYPE_Q); 80 } 81 82 /*static*/ 83 bool SVGLength::IsFontRelativeUnit(uint8_t aUnit) { 84 return aUnit == SVG_LENGTHTYPE_EMS || aUnit == SVG_LENGTHTYPE_EXS || 85 (aUnit >= SVG_LENGTHTYPE_CH && aUnit <= SVG_LENGTHTYPE_RCAP); 86 } 87 88 /** 89 * Helper to convert between different CSS absolute units without the need for 90 * an element, which provides more flexibility at the DOM level (and without 91 * the need for an intermediary conversion to user units, which avoids 92 * unnecessary overhead and rounding error). 93 * 94 * Example usage: to find out how many centimeters there are per inch: 95 * 96 * GetAbsUnitsPerAbsUnit(SVG_LENGTHTYPE_CM, SVG_LENGTHTYPE_IN) 97 */ 98 /*static*/ 99 float SVGLength::GetAbsUnitsPerAbsUnit(uint8_t aUnits, uint8_t aPerUnit) { 100 MOZ_ASSERT(SVGLength::IsAbsoluteUnit(aUnits), "Not a CSS absolute unit"); 101 MOZ_ASSERT(SVGLength::IsAbsoluteUnit(aPerUnit), "Not a CSS absolute unit"); 102 103 static const float CSSAbsoluteUnitConversionFactors[7][7] = { 104 // columns: px, cm, mm, in, pt, pc, q 105 // px per...: 106 {1.0f, 37.7952755906f, 3.779528f, 96.0f, 1.33333333333333333f, 16.0f, 107 0.94488188988f}, 108 // cm per...: 109 {0.02645833333f, 1.0f, 0.1f, 2.54f, 0.035277777777777778f, 110 0.42333333333333333f, 0.025f}, 111 // mm per...: 112 {0.26458333333f, 10.0f, 1.0f, 25.4f, 0.35277777777777778f, 113 4.2333333333333333f, 0.25f}, 114 // in per...: 115 {0.01041666666f, 0.39370078740157481f, 0.039370078740157481f, 1.0f, 116 0.013888888888888889f, 0.16666666666666667f, 0.02204860853f}, 117 // pt per...: 118 {0.75f, 28.346456692913386f, 2.8346456692913386f, 72.0f, 1.0f, 12.0f, 119 0.70866141732f}, 120 // pc per...: 121 {0.0625f, 2.3622047244094489f, 0.23622047244094489f, 6.0f, 122 0.083333333333333333f, 1.0f, 16.9333333333f}, 123 // q per...: 124 {1.0583333332f, 40.0f, 4.0f, 45.354336f, 1.41111111111f, 16.9333333333f, 125 1.0f}}; 126 127 auto ToIndex = [](uint8_t aUnit) { 128 return aUnit == SVG_LENGTHTYPE_NUMBER ? 0 : aUnit - 5; 129 }; 130 131 return CSSAbsoluteUnitConversionFactors[ToIndex(aUnits)][ToIndex(aPerUnit)]; 132 } 133 134 float SVGLength::GetValueInSpecifiedUnit(uint8_t aUnit, 135 const SVGElement* aElement, 136 uint8_t aAxis) const { 137 if (aUnit == mUnit) { 138 return mValue; 139 } 140 if ((aUnit == SVG_LENGTHTYPE_NUMBER && mUnit == SVG_LENGTHTYPE_PX) || 141 (aUnit == SVG_LENGTHTYPE_PX && mUnit == SVG_LENGTHTYPE_NUMBER)) { 142 return mValue; 143 } 144 if (IsAbsoluteUnit(aUnit) && IsAbsoluteUnit(mUnit)) { 145 return mValue * GetAbsUnitsPerAbsUnit(aUnit, mUnit); 146 } 147 148 // Otherwise we do a two step conversion via user units. This can only 149 // succeed if aElement is non-null (although that's not sufficient to 150 // guarantee success). 151 152 SVGElementMetrics userSpaceMetrics(aElement); 153 154 float userUnitsPerCurrentUnit = GetPixelsPerUnit(userSpaceMetrics, aAxis); 155 float userUnitsPerNewUnit = 156 SVGLength(0.0f, aUnit).GetPixelsPerUnit(userSpaceMetrics, aAxis); 157 158 float value = mValue * userUnitsPerCurrentUnit / userUnitsPerNewUnit; 159 160 // userUnitsPerCurrentUnit could be infinity, or userUnitsPerNewUnit could 161 // be zero. 162 if (std::isfinite(value)) { 163 return value; 164 } 165 return std::numeric_limits<float>::quiet_NaN(); 166 } 167 168 // Helpers: 169 170 enum class ZoomType { Self, SelfFromRoot, None }; 171 172 /*static*/ 173 float SVGLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics, 174 uint8_t aUnitType, uint8_t aAxis, 175 bool aApplyZoom) { 176 auto zoomType = ZoomType::Self; 177 float value = [&]() -> float { 178 switch (aUnitType) { 179 case SVG_LENGTHTYPE_NUMBER: 180 case SVG_LENGTHTYPE_PX: 181 return 1.0f; 182 case SVG_LENGTHTYPE_PERCENTAGE: 183 zoomType = ZoomType::None; 184 return aMetrics.GetAxisLength(aAxis) / 100.0f; 185 case SVG_LENGTHTYPE_EMS: 186 zoomType = ZoomType::None; 187 return aMetrics.GetEmLength(UserSpaceMetrics::Type::This); 188 case SVG_LENGTHTYPE_EXS: 189 zoomType = ZoomType::None; 190 return aMetrics.GetExLength(UserSpaceMetrics::Type::This); 191 case SVG_LENGTHTYPE_CH: 192 zoomType = ZoomType::None; 193 return aMetrics.GetChSize(UserSpaceMetrics::Type::This); 194 case SVG_LENGTHTYPE_REM: 195 zoomType = ZoomType::SelfFromRoot; 196 return aMetrics.GetEmLength(UserSpaceMetrics::Type::Root); 197 case SVG_LENGTHTYPE_IC: 198 zoomType = ZoomType::None; 199 return aMetrics.GetIcWidth(UserSpaceMetrics::Type::This); 200 case SVG_LENGTHTYPE_CAP: 201 zoomType = ZoomType::None; 202 return aMetrics.GetCapHeight(UserSpaceMetrics::Type::This); 203 case SVG_LENGTHTYPE_VW: 204 return aMetrics.GetCSSViewportSize().width / 100.f; 205 case SVG_LENGTHTYPE_VH: 206 return aMetrics.GetCSSViewportSize().height / 100.f; 207 case SVG_LENGTHTYPE_VMIN: { 208 auto sz = aMetrics.GetCSSViewportSize(); 209 return std::min(sz.width, sz.height) / 100.f; 210 } 211 case SVG_LENGTHTYPE_VMAX: { 212 auto sz = aMetrics.GetCSSViewportSize(); 213 return std::max(sz.width, sz.height) / 100.f; 214 } 215 case SVG_LENGTHTYPE_LH: 216 zoomType = ZoomType::None; 217 return aMetrics.GetLineHeight(UserSpaceMetrics::Type::This); 218 case SVG_LENGTHTYPE_RLH: 219 zoomType = ZoomType::SelfFromRoot; 220 return aMetrics.GetLineHeight(UserSpaceMetrics::Type::Root); 221 case SVG_LENGTHTYPE_REX: 222 zoomType = ZoomType::SelfFromRoot; 223 return aMetrics.GetExLength(UserSpaceMetrics::Type::Root); 224 case SVG_LENGTHTYPE_RCH: 225 zoomType = ZoomType::SelfFromRoot; 226 return aMetrics.GetChSize(UserSpaceMetrics::Type::Root); 227 case SVG_LENGTHTYPE_RIC: 228 zoomType = ZoomType::SelfFromRoot; 229 return aMetrics.GetIcWidth(UserSpaceMetrics::Type::Root); 230 case SVG_LENGTHTYPE_RCAP: 231 zoomType = ZoomType::SelfFromRoot; 232 return aMetrics.GetCapHeight(UserSpaceMetrics::Type::Root); 233 default: 234 MOZ_ASSERT(IsAbsoluteUnit(aUnitType)); 235 return GetAbsUnitsPerAbsUnit(SVG_LENGTHTYPE_PX, aUnitType); 236 } 237 }(); 238 if (aApplyZoom) { 239 switch (zoomType) { 240 case ZoomType::None: 241 break; 242 case ZoomType::Self: 243 value *= aMetrics.GetZoom(); 244 break; 245 case ZoomType::SelfFromRoot: 246 value *= aMetrics.GetZoom() / aMetrics.GetRootZoom(); 247 break; 248 } 249 } 250 return value; 251 } 252 253 /*static*/ 254 float SVGLength::GetPixelsPerCSSUnit(const UserSpaceMetrics& aMetrics, 255 nsCSSUnit aCSSUnit, uint8_t aAxis, 256 bool aApplyZoom) { 257 uint8_t unitType; 258 switch (aCSSUnit) { 259 #define SVG_LENGTH_EMPTY_UNIT(id, cssValue) 260 #define SVG_LENGTH_UNIT(id, name, cssValue) \ 261 case cssValue: \ 262 unitType = id; \ 263 break; 264 #include "mozilla/dom/SVGLengthUnits.h" 265 #undef SVG_LENGTH_UNIT 266 #undef SVG_LENGTH_EMPTY_UNIT 267 default: 268 MOZ_ASSERT_UNREACHABLE("Unknown CSS unit to SVG mapping"); 269 unitType = SVG_LENGTHTYPE_UNKNOWN; 270 break; 271 } 272 return GetPixelsPerUnit(aMetrics, unitType, aAxis, aApplyZoom); 273 } 274 275 /* static */ 276 nsCSSUnit SVGLength::SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit) { 277 switch (aSpecifiedUnit) { 278 #define SVG_LENGTH_EMPTY_UNIT(id, cssValue) \ 279 case id: \ 280 return cssValue; 281 #define SVG_LENGTH_UNIT(id, name, cssValue) SVG_LENGTH_EMPTY_UNIT(id, cssValue) 282 #include "mozilla/dom/SVGLengthUnits.h" 283 #undef SVG_LENGTH_UNIT 284 #undef SVG_LENGTH_EMPTY_UNIT 285 default: 286 MOZ_ASSERT_UNREACHABLE("Unknown unit type"); 287 return nsCSSUnit::eCSSUnit_Pixel; 288 } 289 } 290 291 /* static */ 292 void SVGLength::GetUnitString(nsAString& aUnit, uint16_t aUnitType) { 293 switch (aUnitType) { 294 #define SVG_LENGTH_EMPTY_UNIT(id, cssValue) \ 295 case id: \ 296 aUnit.Truncate(); \ 297 return; 298 #define SVG_LENGTH_UNIT(id, name, cssValue) \ 299 case id: \ 300 aUnit.AssignLiteral(name); \ 301 return; 302 #include "mozilla/dom/SVGLengthUnits.h" 303 #undef SVG_LENGTH_UNIT 304 #undef SVG_LENGTH_EMPTY_UNIT 305 } 306 MOZ_ASSERT_UNREACHABLE( 307 "Unknown unit type! Someone's using an SVGLength " 308 "with an invalid unit?"); 309 } 310 311 /* static */ 312 uint16_t SVGLength::GetUnitTypeForString(const nsAString& aUnit) { 313 #define SVG_LENGTH_EMPTY_UNIT(id, cssValue) \ 314 if (aUnit.IsEmpty()) { \ 315 return id; \ 316 } 317 #define SVG_LENGTH_UNIT(id, name, cssValue) \ 318 if (aUnit.LowerCaseEqualsLiteral(name)) { \ 319 return id; \ 320 } 321 #include "mozilla/dom/SVGLengthUnits.h" 322 #undef SVG_LENGTH_UNIT 323 #undef SVG_LENGTH_EMPTY_UNIT 324 325 return SVG_LENGTHTYPE_UNKNOWN; 326 } 327 328 } // namespace mozilla