// Runtime checks and cleanup for value types in the spec that aren't easily expressed through our type system. // These will help us to prevent silent failures and corrupted documents. // // // export function decimalNumber(val: number): number { if (isNaN(val)) { throw new Error(`Invalid value '${val}' specified. Must be an integer.`); } return Math.floor(val); } // // // export function unsignedDecimalNumber(val: number): number { const value = decimalNumber(val); if (value < 0) { throw new Error(`Invalid value '${val}' specified. Must be a positive integer.`); } return value; } // // // // // export function universalMeasureValue(val: string): string { const unit = val.slice(-2); if (!universalMeasureUnits.includes(unit)) { throw new Error(`Invalid unit '${unit}' specified. Valid units are ${universalMeasureUnits.join(", ")}`); } const amount = val.substring(0, val.length - 2); if (isNaN(Number(amount))) { throw new Error(`Invalid value '${amount}' specified. Expected a valid number.`); } return `${Number(amount)}${unit}`; } const universalMeasureUnits = ["mm", "cm", "in", "pt", "pc", "pi"]; // // // // // export function positiveUniversalMeasureValue(val: string): string { const value = universalMeasureValue(val); if (parseFloat(value) < 0) { throw new Error(`Invalid value '${value}' specified. Expected a positive number.`); } return value; } // // // // // // // // // The xsd:hexBinary type represents binary data as a sequence of binary octets. // It uses hexadecimal encoding, where each binary octet is a two-character hexadecimal number. // Lowercase and uppercase letters A through F are permitted. For example, 0FB8 and 0fb8 are two // equal xsd:hexBinary representations consisting of two octets. // // // // // export function hexColorValue(val: string): string { if (val === "auto") { return val; } // It's super common to see colors prefixed with a pound, but technically invalid here. // Most clients work with it, but strip it off anyway for strict compliance. const color = val.charAt(0) === "#" ? val.substring(1) : val; if (color.length !== 6 || isNaN(Number("0x" + color))) { throw new Error(`Invalid color value '${color}'. Expected six digit hex value (eg FF9900)`); } return color; } // // // export function signedTwipsMeasureValue(val: string | number): string | number { return typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val); } // // // export function hpsMeasureValue(val: string | number): string | number { return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); } // // // export function twipsMeasureValue(val: string | number): string | number { return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); } // // // // // export function percentageValue(val: string): string { if (val.slice(-1) !== "%") { throw new Error(`Invalid value '${val}'. Expected percentage value (eg '55%')`); } const percent = val.substring(0, val.length - 1); if (isNaN(Number(percent))) { throw new Error(`Invalid value '${percent}' specified. Expected a valid number.`); } return `${Number(percent)}%`; } // // // // // // // // // export function measurementOrPercentValue(val: number | string): number | string { return typeof val === "number" ? decimalNumber(val) : percentageValue(val); }