svgdom
Version:
Straightforward DOM implementation for SVG, HTML and XML
152 lines (133 loc) • 4.41 kB
JavaScript
// @ts-check
// @ts-ignore
import { extendStatic } from '../../utils/objectCreationUtils.js'
const unitTypes = {
SVG_LENGTHTYPE_UNKNOWN: 0,
SVG_LENGTHTYPE_NUMBER: 1,
SVG_LENGTHTYPE_PERCENTAGE: 2,
SVG_LENGTHTYPE_EMS: 3,
SVG_LENGTHTYPE_EXS: 4,
SVG_LENGTHTYPE_PX: 5,
SVG_LENGTHTYPE_CM: 6,
SVG_LENGTHTYPE_MM: 7,
SVG_LENGTHTYPE_IN: 8,
SVG_LENGTHTYPE_PT: 9,
SVG_LENGTHTYPE_PC: 10,
}
const unitByString = {
['']: unitTypes.SVG_LENGTHTYPE_NUMBER,
['%']: unitTypes.SVG_LENGTHTYPE_PERCENTAGE,
['em']: unitTypes.SVG_LENGTHTYPE_EMS,
['ex']: unitTypes.SVG_LENGTHTYPE_EXS,
['px']: unitTypes.SVG_LENGTHTYPE_PX,
['cm']: unitTypes.SVG_LENGTHTYPE_CM,
['mm']: unitTypes.SVG_LENGTHTYPE_MM,
['in']: unitTypes.SVG_LENGTHTYPE_IN,
['pt']: unitTypes.SVG_LENGTHTYPE_PT,
['pc']: unitTypes.SVG_LENGTHTYPE_PC,
}
const unitStringByConstant = new Map(
Object.entries(unitByString).map(([unitString, unitConstant]) => [
unitConstant,
unitString,
])
)
const unitFactors = new Map([
[unitTypes.SVG_LENGTHTYPE_NUMBER, 1],
[unitTypes.SVG_LENGTHTYPE_PERCENTAGE, NaN],
[unitTypes.SVG_LENGTHTYPE_EMS, NaN],
[unitTypes.SVG_LENGTHTYPE_EXS, NaN],
[unitTypes.SVG_LENGTHTYPE_PX, 1],
[unitTypes.SVG_LENGTHTYPE_CM, 6],
[unitTypes.SVG_LENGTHTYPE_MM, 96 / 25.4],
[unitTypes.SVG_LENGTHTYPE_IN, 96],
[unitTypes.SVG_LENGTHTYPE_PT, 4 / 3],
[unitTypes.SVG_LENGTHTYPE_PC, 16],
])
const valuePattern = /^\s*([+-]?[0-9]*[.]?[0-9]+(?:e[+-]?[0-9]+)?)(em|ex|px|in|cm|mm|pt|pc|%)?\s*$/i;
export class SVGLength {
element
attributeName
/**
* @param {Element} element
* @param {string} attributeName
*/
constructor(element, attributeName) {
this.element = element
this.attributeName = attributeName
}
get unitType() {
return parseValue(this.element.getAttribute(this.attributeName))[1]
}
get value() {
const [value, unit] = parseValue(
this.element.getAttribute(this.attributeName)
)
return value * getUnitFactor(unit)
}
set value(value) {
const unitFactor = getUnitFactor(this.unitType)
this.element.setAttribute(
this.attributeName,
value / unitFactor + unitString(this)
)
}
get valueInSpecifiedUnits() {
return parseValue(this.element.getAttribute(this.attributeName))[0]
}
set valueInSpecifiedUnits(value) {
this.element.setAttribute(this.attributeName, value + unitString(this))
}
get valueAsString() {
// Do not simply use getAttribute() as this function has to return a string
// that is a valid representation of the used value.
return this.valueInSpecifiedUnits + unitString(this)
}
set valueAsString(valueString) {
const [value, unit] = parseValue(valueString, false)
const unitString = unitStringByConstant.get(unit) || ''
this.element.setAttribute(this.attributeName, value + unitString)
}
}
/**
* @param {string|null} valueString
* @param {boolean} fallback If set to `false` causes an error to be thrown if
* `valueString` can not be parsed properly. Otherwise the returned value falls
* back to 0 and the unit falls back to `SVG_LENGTHTYPE_NUMBER`.
* @return {[number, number]} Value and unit. For unknown units, if the
* attribute is not of the correct format or if the attribute is not present on
* the element, value 0 and unit SVG_LENGTHTYPE_NUMBER are returned.
*/
function parseValue(valueString, fallback = true) {
const [, rawValue, rawUnit] = (valueString || '').match(valuePattern) || []
const unit = unitByString[(rawUnit || '').toLowerCase()]
if (rawValue !== undefined && unit !== undefined) {
return [parseFloat(rawValue), unit]
}
if (fallback) {
// For unknown units or unparsable attributes, browsers fall back to value 0
return [0, unitTypes.SVG_LENGTHTYPE_NUMBER]
}
throw new Error('An invalid or illegal string was specified')
}
/**
* @param {number} unit Unit constant
*/
function getUnitFactor(unit) {
const unitFactor = unitFactors.get(unit)
if (unitFactor === undefined) {
throw new Error(unitFactor + ' is not a known unit constant')
}
if (isNaN(unitFactor)) {
throw new Error(`Unit ${unitStringByConstant.get(unit)} is not supported`)
}
return unitFactor
}
/**
* @param {SVGLength} svgLength
* @return {string}
*/
function unitString(svgLength) {
return unitStringByConstant.get(svgLength.unitType) || ''
}
extendStatic(SVGLength, unitTypes)