diff options
Diffstat (limited to '')
-rw-r--r-- | dom/svg/SVGLength.cpp | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/dom/svg/SVGLength.cpp b/dom/svg/SVGLength.cpp new file mode 100644 index 0000000000..f7c383a16f --- /dev/null +++ b/dom/svg/SVGLength.cpp @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGLength.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsCSSValue.h" +#include "nsTextFormatter.h" +#include "SVGContentUtils.h" +#include <limits> +#include <algorithm> + +using namespace mozilla::dom; +using namespace mozilla::dom::SVGLength_Binding; + +namespace mozilla { + +const unsigned short SVG_LENGTHTYPE_Q = 11; +const unsigned short SVG_LENGTHTYPE_CH = 12; +const unsigned short SVG_LENGTHTYPE_REM = 13; +const unsigned short SVG_LENGTHTYPE_IC = 14; +const unsigned short SVG_LENGTHTYPE_CAP = 15; +const unsigned short SVG_LENGTHTYPE_VW = 16; +const unsigned short SVG_LENGTHTYPE_VH = 17; +const unsigned short SVG_LENGTHTYPE_VMIN = 18; +const unsigned short SVG_LENGTHTYPE_VMAX = 19; + +void SVGLength::GetValueAsString(nsAString& aValue) const { + nsTextFormatter::ssprintf(aValue, u"%g", (double)mValue); + + nsAutoString unitString; + GetUnitString(unitString, mUnit); + aValue.Append(unitString); +} + +bool SVGLength::SetValueFromString(const nsAString& aString) { + bool success; + auto token = SVGContentUtils::GetAndEnsureOneToken(aString, success); + + if (!success) { + return false; + } + + RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(token); + const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(token); + + float value; + + if (!SVGContentUtils::ParseNumber(iter, end, value)) { + return false; + } + + const nsAString& units = Substring(iter.get(), end.get()); + uint16_t unitType = GetUnitTypeForString(units); + if (unitType == SVG_LENGTHTYPE_UNKNOWN) { + return false; + } + mValue = value; + mUnit = uint8_t(unitType); + return true; +} + +/*static*/ +bool SVGLength::IsAbsoluteUnit(uint8_t aUnit) { + return aUnit == SVG_LENGTHTYPE_NUMBER || + (aUnit >= SVG_LENGTHTYPE_PX && aUnit <= SVG_LENGTHTYPE_Q); +} + +/*static*/ +bool SVGLength::IsFontRelativeUnit(uint8_t aUnit) { + return aUnit == SVG_LENGTHTYPE_EMS || aUnit == SVG_LENGTHTYPE_EXS || + (aUnit >= SVG_LENGTHTYPE_CH && aUnit <= SVG_LENGTHTYPE_CAP); +} + +/** + * Helper to convert between different CSS absolute units without the need for + * an element, which provides more flexibility at the DOM level (and without + * the need for an intermediary conversion to user units, which avoids + * unnecessary overhead and rounding error). + * + * Example usage: to find out how many centimeters there are per inch: + * + * GetAbsUnitsPerAbsUnit(SVG_LENGTHTYPE_CM, SVG_LENGTHTYPE_IN) + */ +/*static*/ +float SVGLength::GetAbsUnitsPerAbsUnit(uint8_t aUnits, uint8_t aPerUnit) { + MOZ_ASSERT(SVGLength::IsAbsoluteUnit(aUnits), "Not a CSS absolute unit"); + MOZ_ASSERT(SVGLength::IsAbsoluteUnit(aPerUnit), "Not a CSS absolute unit"); + + static const float CSSAbsoluteUnitConversionFactors[7][7] = { + // columns: px, cm, mm, in, pt, pc, q + // px per...: + {1.0f, 37.7952755906f, 3.779528f, 96.0f, 1.33333333333333333f, 16.0f, + 0.94488188988f}, + // cm per...: + {0.02645833333f, 1.0f, 0.1f, 2.54f, 0.035277777777777778f, + 0.42333333333333333f, 0.025f}, + // mm per...: + {0.26458333333f, 10.0f, 1.0f, 25.4f, 0.35277777777777778f, + 4.2333333333333333f, 0.25f}, + // in per...: + {0.01041666666f, 0.39370078740157481f, 0.039370078740157481f, 1.0f, + 0.013888888888888889f, 0.16666666666666667f, 0.02204860853f}, + // pt per...: + {0.75f, 28.346456692913386f, 2.8346456692913386f, 72.0f, 1.0f, 12.0f, + 0.70866141732f}, + // pc per...: + {0.0625f, 2.3622047244094489f, 0.23622047244094489f, 6.0f, + 0.083333333333333333f, 1.0f, 16.9333333333f}, + // q per...: + {1.0583333332f, 40.0f, 4.0f, 45.354336f, 1.41111111111f, 16.9333333333f, + 1.0f}}; + + auto ToIndex = [](uint8_t aUnit) { + return aUnit == SVG_LENGTHTYPE_NUMBER ? 0 : aUnit - 5; + }; + + return CSSAbsoluteUnitConversionFactors[ToIndex(aUnits)][ToIndex(aPerUnit)]; +} + +float SVGLength::GetValueInSpecifiedUnit(uint8_t aUnit, + const SVGElement* aElement, + uint8_t aAxis) const { + if (aUnit == mUnit) { + return mValue; + } + if ((aUnit == SVG_LENGTHTYPE_NUMBER && mUnit == SVG_LENGTHTYPE_PX) || + (aUnit == SVG_LENGTHTYPE_PX && mUnit == SVG_LENGTHTYPE_NUMBER)) { + return mValue; + } + if (IsAbsoluteUnit(aUnit) && IsAbsoluteUnit(mUnit)) { + return mValue * GetAbsUnitsPerAbsUnit(aUnit, mUnit); + } + + // Otherwise we do a two step conversion via user units. This can only + // succeed if aElement is non-null (although that's not sufficient to + // guarantee success). + + SVGElementMetrics userSpaceMetrics(aElement); + + float userUnitsPerCurrentUnit = GetPixelsPerUnit(userSpaceMetrics, aAxis); + float userUnitsPerNewUnit = + SVGLength(0.0f, aUnit).GetPixelsPerUnit(userSpaceMetrics, aAxis); + + float value = mValue * userUnitsPerCurrentUnit / userUnitsPerNewUnit; + + // userUnitsPerCurrentUnit could be infinity, or userUnitsPerNewUnit could + // be zero. + if (std::isfinite(value)) { + return value; + } + return std::numeric_limits<float>::quiet_NaN(); +} + +// Helpers: + +/*static*/ +float SVGLength::GetPixelsPerUnit(const UserSpaceMetrics& aMetrics, + uint8_t aUnitType, uint8_t aAxis) { + switch (aUnitType) { + case SVG_LENGTHTYPE_NUMBER: + case SVG_LENGTHTYPE_PX: + return 1.0f; + case SVG_LENGTHTYPE_PERCENTAGE: + return aMetrics.GetAxisLength(aAxis) / 100.0f; + case SVG_LENGTHTYPE_EMS: + return aMetrics.GetEmLength(UserSpaceMetrics::Type::This); + case SVG_LENGTHTYPE_EXS: + return aMetrics.GetExLength(UserSpaceMetrics::Type::This); + case SVG_LENGTHTYPE_CH: + return aMetrics.GetChSize(UserSpaceMetrics::Type::This); + case SVG_LENGTHTYPE_REM: + return aMetrics.GetEmLength(UserSpaceMetrics::Type::Root); + case SVG_LENGTHTYPE_IC: + return aMetrics.GetIcWidth(UserSpaceMetrics::Type::This); + case SVG_LENGTHTYPE_CAP: + return aMetrics.GetCapHeight(UserSpaceMetrics::Type::This); + case SVG_LENGTHTYPE_VW: + return aMetrics.GetCSSViewportSize().width / 100.f; + case SVG_LENGTHTYPE_VH: + return aMetrics.GetCSSViewportSize().height / 100.f; + case SVG_LENGTHTYPE_VMIN: { + auto sz = aMetrics.GetCSSViewportSize(); + return std::min(sz.width, sz.height) / 100.f; + } + case SVG_LENGTHTYPE_VMAX: { + auto sz = aMetrics.GetCSSViewportSize(); + return std::max(sz.width, sz.height) / 100.f; + } + default: + MOZ_ASSERT(IsAbsoluteUnit(aUnitType)); + return GetAbsUnitsPerAbsUnit(SVG_LENGTHTYPE_PX, aUnitType); + } +} + +/* static */ +nsCSSUnit SVGLength::SpecifiedUnitTypeToCSSUnit(uint8_t aSpecifiedUnit) { + switch (aSpecifiedUnit) { + case SVG_LENGTHTYPE_NUMBER: + case SVG_LENGTHTYPE_PX: + return nsCSSUnit::eCSSUnit_Pixel; + + case SVG_LENGTHTYPE_MM: + return nsCSSUnit::eCSSUnit_Millimeter; + + case SVG_LENGTHTYPE_CM: + return nsCSSUnit::eCSSUnit_Centimeter; + + case SVG_LENGTHTYPE_IN: + return nsCSSUnit::eCSSUnit_Inch; + + case SVG_LENGTHTYPE_PT: + return nsCSSUnit::eCSSUnit_Point; + + case SVG_LENGTHTYPE_PC: + return nsCSSUnit::eCSSUnit_Pica; + + case SVG_LENGTHTYPE_PERCENTAGE: + return nsCSSUnit::eCSSUnit_Percent; + + case SVG_LENGTHTYPE_EMS: + return nsCSSUnit::eCSSUnit_EM; + + case SVG_LENGTHTYPE_EXS: + return nsCSSUnit::eCSSUnit_XHeight; + + case SVG_LENGTHTYPE_Q: + return nsCSSUnit::eCSSUnit_Quarter; + + case SVG_LENGTHTYPE_CH: + return nsCSSUnit::eCSSUnit_Char; + + case SVG_LENGTHTYPE_REM: + return nsCSSUnit::eCSSUnit_RootEM; + + case SVG_LENGTHTYPE_IC: + return nsCSSUnit::eCSSUnit_Ideographic; + + case SVG_LENGTHTYPE_CAP: + return nsCSSUnit::eCSSUnit_CapHeight; + + case SVG_LENGTHTYPE_VW: + return nsCSSUnit::eCSSUnit_VW; + + case SVG_LENGTHTYPE_VH: + return nsCSSUnit::eCSSUnit_VH; + + case SVG_LENGTHTYPE_VMIN: + return nsCSSUnit::eCSSUnit_VMin; + + case SVG_LENGTHTYPE_VMAX: + return nsCSSUnit::eCSSUnit_VMax; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit type"); + return nsCSSUnit::eCSSUnit_Pixel; + } +} + +/* static */ +void SVGLength::GetUnitString(nsAString& aUnit, uint16_t aUnitType) { + switch (aUnitType) { + case SVG_LENGTHTYPE_NUMBER: + aUnit.Truncate(); + return; + case SVG_LENGTHTYPE_PERCENTAGE: + aUnit.AssignLiteral("%"); + return; + case SVG_LENGTHTYPE_EMS: + aUnit.AssignLiteral("em"); + return; + case SVG_LENGTHTYPE_EXS: + aUnit.AssignLiteral("ex"); + return; + case SVG_LENGTHTYPE_PX: + aUnit.AssignLiteral("px"); + return; + case SVG_LENGTHTYPE_CM: + aUnit.AssignLiteral("cm"); + return; + case SVG_LENGTHTYPE_MM: + aUnit.AssignLiteral("mm"); + return; + case SVG_LENGTHTYPE_IN: + aUnit.AssignLiteral("in"); + return; + case SVG_LENGTHTYPE_PT: + aUnit.AssignLiteral("pt"); + return; + case SVG_LENGTHTYPE_PC: + aUnit.AssignLiteral("pc"); + return; + case SVG_LENGTHTYPE_Q: + aUnit.AssignLiteral("q"); + return; + case SVG_LENGTHTYPE_CH: + aUnit.AssignLiteral("ch"); + return; + case SVG_LENGTHTYPE_REM: + aUnit.AssignLiteral("rem"); + return; + case SVG_LENGTHTYPE_IC: + aUnit.AssignLiteral("ic"); + return; + case SVG_LENGTHTYPE_CAP: + aUnit.AssignLiteral("cap"); + return; + case SVG_LENGTHTYPE_VW: + aUnit.AssignLiteral("vw"); + return; + case SVG_LENGTHTYPE_VH: + aUnit.AssignLiteral("vh"); + return; + case SVG_LENGTHTYPE_VMIN: + aUnit.AssignLiteral("vmin"); + return; + case SVG_LENGTHTYPE_VMAX: + aUnit.AssignLiteral("vmax"); + return; + } + MOZ_ASSERT_UNREACHABLE( + "Unknown unit type! Someone's using an SVGLength " + "with an invalid unit?"); +} + +/* static */ +uint16_t SVGLength::GetUnitTypeForString(const nsAString& aUnit) { + if (aUnit.IsEmpty()) { + return SVG_LENGTHTYPE_NUMBER; + } + if (aUnit.EqualsLiteral("%")) { + return SVG_LENGTHTYPE_PERCENTAGE; + } + if (aUnit.LowerCaseEqualsLiteral("em")) { + return SVG_LENGTHTYPE_EMS; + } + if (aUnit.LowerCaseEqualsLiteral("ex")) { + return SVG_LENGTHTYPE_EXS; + } + if (aUnit.LowerCaseEqualsLiteral("px")) { + return SVG_LENGTHTYPE_PX; + } + if (aUnit.LowerCaseEqualsLiteral("cm")) { + return SVG_LENGTHTYPE_CM; + } + if (aUnit.LowerCaseEqualsLiteral("mm")) { + return SVG_LENGTHTYPE_MM; + } + if (aUnit.LowerCaseEqualsLiteral("in")) { + return SVG_LENGTHTYPE_IN; + } + if (aUnit.LowerCaseEqualsLiteral("pt")) { + return SVG_LENGTHTYPE_PT; + } + if (aUnit.LowerCaseEqualsLiteral("pc")) { + return SVG_LENGTHTYPE_PC; + } + if (aUnit.LowerCaseEqualsLiteral("q")) { + return SVG_LENGTHTYPE_Q; + } + if (aUnit.LowerCaseEqualsLiteral("ch")) { + return SVG_LENGTHTYPE_CH; + } + if (aUnit.LowerCaseEqualsLiteral("rem")) { + return SVG_LENGTHTYPE_REM; + } + if (aUnit.LowerCaseEqualsLiteral("ic")) { + return SVG_LENGTHTYPE_IC; + } + if (aUnit.LowerCaseEqualsLiteral("cap")) { + return SVG_LENGTHTYPE_CAP; + } + if (aUnit.LowerCaseEqualsLiteral("vw")) { + return SVG_LENGTHTYPE_VW; + } + if (aUnit.LowerCaseEqualsLiteral("vh")) { + return SVG_LENGTHTYPE_VH; + } + if (aUnit.LowerCaseEqualsLiteral("vmin")) { + return SVG_LENGTHTYPE_VMIN; + } + if (aUnit.LowerCaseEqualsLiteral("vmax")) { + return SVG_LENGTHTYPE_VMAX; + } + return SVG_LENGTHTYPE_UNKNOWN; +} + +} // namespace mozilla |