/* -*- 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 "mozilla/ArrayUtils.h" #include "SVGLength.h" #include "nsSVGElement.h" #include "mozilla/dom/SVGSVGElement.h" #include "nsTextFormatter.h" #include "SVGContentUtils.h" #include <limits> #include <algorithm> namespace mozilla { // Declare some helpers defined below: static void GetUnitString(nsAString& unit, uint16_t unitType); static uint16_t GetUnitTypeForString(const nsAString& unitStr); void SVGLength::GetValueAsString(nsAString &aValue) const { char16_t buf[24]; nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(char16_t), u"%g", (double)mValue); aValue.Assign(buf); nsAutoString unitString; GetUnitString(unitString, mUnit); aValue.Append(unitString); } bool SVGLength::SetValueFromString(const nsAString &aString) { RangedPtr<const char16_t> iter = SVGContentUtils::GetStartRangedPtr(aString); const RangedPtr<const char16_t> end = SVGContentUtils::GetEndRangedPtr(aString); float value; if (!SVGContentUtils::ParseNumber(iter, end, value)) { return false; } const nsAString& units = Substring(iter.get(), end.get()); uint16_t unitType = GetUnitTypeForString(units); if (!IsValidUnitType(unitType)) { return false; } mValue = value; mUnit = uint8_t(unitType); return true; } inline static bool IsAbsoluteUnit(uint8_t aUnit) { return aUnit >= nsIDOMSVGLength::SVG_LENGTHTYPE_CM && aUnit <= nsIDOMSVGLength::SVG_LENGTHTYPE_PC; } /** * 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(nsIDOMSVGLength::SVG_LENGTHTYPE_CM, * nsIDOMSVGLength::SVG_LENGTHTYPE_IN) */ inline static float GetAbsUnitsPerAbsUnit(uint8_t aUnits, uint8_t aPerUnit) { MOZ_ASSERT(IsAbsoluteUnit(aUnits), "Not a CSS absolute unit"); MOZ_ASSERT(IsAbsoluteUnit(aPerUnit), "Not a CSS absolute unit"); float CSSAbsoluteUnitConversionFactors[5][5] = { // columns: cm, mm, in, pt, pc // cm per...: { 1.0f, 0.1f, 2.54f, 0.035277777777777778f, 0.42333333333333333f }, // mm per...: { 10.0f, 1.0f, 25.4f, 0.35277777777777778f, 4.2333333333333333f }, // in per...: { 0.39370078740157481f, 0.039370078740157481f, 1.0f, 0.013888888888888889f, 0.16666666666666667f }, // pt per...: { 28.346456692913386f, 2.8346456692913386f, 72.0f, 1.0f, 12.0f }, // pc per...: { 2.3622047244094489f, 0.23622047244094489f, 6.0f, 0.083333333333333333f, 1.0f } }; // First absolute unit is SVG_LENGTHTYPE_CM = 6 return CSSAbsoluteUnitConversionFactors[aUnits - 6][aPerUnit - 6]; } float SVGLength::GetValueInSpecifiedUnit(uint8_t aUnit, const nsSVGElement *aElement, uint8_t aAxis) const { if (aUnit == mUnit) { return mValue; } if ((aUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER && mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_PX) || (aUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_PX && mUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER)) { return mValue; } if (IsAbsoluteUnit(aUnit) && IsAbsoluteUnit(mUnit)) { return mValue * GetAbsUnitsPerAbsUnit(aUnit, mUnit); } // Otherwise we do a two step convertion via user units. This can only // succeed if aElement is non-null (although that's not sufficent to // guarantee success). float userUnitsPerCurrentUnit = GetUserUnitsPerUnit(aElement, aAxis); float userUnitsPerNewUnit = SVGLength(0.0f, aUnit).GetUserUnitsPerUnit(aElement, aAxis); NS_ASSERTION(userUnitsPerCurrentUnit >= 0 || !IsFinite(userUnitsPerCurrentUnit), "bad userUnitsPerCurrentUnit"); NS_ASSERTION(userUnitsPerNewUnit >= 0 || !IsFinite(userUnitsPerNewUnit), "bad userUnitsPerNewUnit"); float value = mValue * userUnitsPerCurrentUnit / userUnitsPerNewUnit; // userUnitsPerCurrentUnit could be infinity, or userUnitsPerNewUnit could // be zero. if (IsFinite(value)) { return value; } return std::numeric_limits<float>::quiet_NaN(); } #define INCHES_PER_MM_FLOAT float(0.0393700787) #define INCHES_PER_CM_FLOAT float(0.393700787) float SVGLength::GetUserUnitsPerUnit(const nsSVGElement *aElement, uint8_t aAxis) const { switch (mUnit) { case nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER: case nsIDOMSVGLength::SVG_LENGTHTYPE_PX: return 1.0f; case nsIDOMSVGLength::SVG_LENGTHTYPE_MM: return INCHES_PER_MM_FLOAT * GetUserUnitsPerInch(); case nsIDOMSVGLength::SVG_LENGTHTYPE_CM: return INCHES_PER_CM_FLOAT * GetUserUnitsPerInch(); case nsIDOMSVGLength::SVG_LENGTHTYPE_IN: return GetUserUnitsPerInch(); case nsIDOMSVGLength::SVG_LENGTHTYPE_PT: return (1.0f/POINTS_PER_INCH_FLOAT) * GetUserUnitsPerInch(); case nsIDOMSVGLength::SVG_LENGTHTYPE_PC: return (12.0f/POINTS_PER_INCH_FLOAT) * GetUserUnitsPerInch(); case nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE: return GetUserUnitsPerPercent(aElement, aAxis); case nsIDOMSVGLength::SVG_LENGTHTYPE_EMS: return SVGContentUtils::GetFontSize(const_cast<nsSVGElement*>(aElement)); case nsIDOMSVGLength::SVG_LENGTHTYPE_EXS: return SVGContentUtils::GetFontXHeight(const_cast<nsSVGElement*>(aElement)); default: NS_NOTREACHED("Unknown unit type"); return std::numeric_limits<float>::quiet_NaN(); } } /* static */ float SVGLength::GetUserUnitsPerPercent(const nsSVGElement *aElement, uint8_t aAxis) { if (aElement) { dom::SVGSVGElement *viewportElement = aElement->GetCtx(); if (viewportElement) { return std::max(viewportElement->GetLength(aAxis) / 100.0f, 0.0f); } } return std::numeric_limits<float>::quiet_NaN(); } // Helpers: // These items must be at the same index as the nsIDOMSVGLength constants! static nsIAtom** const unitMap[] = { nullptr, /* SVG_LENGTHTYPE_UNKNOWN */ nullptr, /* SVG_LENGTHTYPE_NUMBER */ &nsGkAtoms::percentage, &nsGkAtoms::em, &nsGkAtoms::ex, &nsGkAtoms::px, &nsGkAtoms::cm, &nsGkAtoms::mm, &nsGkAtoms::in, &nsGkAtoms::pt, &nsGkAtoms::pc }; static void GetUnitString(nsAString& unit, uint16_t unitType) { if (SVGLength::IsValidUnitType(unitType)) { if (unitMap[unitType]) { (*unitMap[unitType])->ToString(unit); } return; } NS_NOTREACHED("Unknown unit type"); // Someone's using an SVGLength with an invalid unit? return; } static uint16_t GetUnitTypeForString(const nsAString& unitStr) { if (unitStr.IsEmpty()) return nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER; nsIAtom* unitAtom = NS_GetStaticAtom(unitStr); if (unitAtom) { for (uint32_t i = 1 ; i < ArrayLength(unitMap) ; i++) { if (unitMap[i] && *unitMap[i] == unitAtom) { return i; } } } return nsIDOMSVGLength::SVG_LENGTHTYPE_UNKNOWN; } } // namespace mozilla