plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
217 lines • 9.51 kB
JavaScript
/**
* A CSS gradient string parser.
*
* Source:
* https://stackoverflow.com/questions/20215440/parse-css-gradient-rule-with-javascript-regex
* Thanks to Dean Taylor
*
* @date 2025-09-04
* @OriginalAuthor Dean Taylor
* @RefactoredBy Ikaros Kappler, ported to Typescript.
* @version 1.0.0
*/
import { Color } from "../datastructures/Color";
import { ColorGradient } from "../datastructures/ColorGradient";
/**
* The default implementation of `PositionToRatioConverter`.
* @param positionString
* @returns
*/
export const DefaultPositionConverter = (positionString) => {
if (typeof positionString === "undefined") {
throw new Error("Cannot parse positioning string `null`.");
}
const tmp = positionString.trim();
if (tmp.length === 0) {
throw new Error('Cannot parse empty positioning string "".');
}
if (!tmp.endsWith("%")) {
throw new Error(`Cannot parse positioning string, must end with '%': '${tmp}'`);
}
return Number.parseFloat(tmp) / 100.0;
};
/**
* Utility combine multiple regular expressions.
*
* Source:
* https://stackoverflow.com/questions/20215440/parse-css-gradient-rule-with-javascript-regex
* Thanks to Dean Taylor
*
* @param {Array<RegExp | string>} regexpList List of regular expressions or strings.
* @param {string} flags Normal RegExp flags.
*/
const __combineRegExp = (regexpList, flags) => {
var source = "";
for (var i = 0; i < regexpList.length; i++) {
const regex = regexpList[i];
if (typeof regex === "string") {
source += regex;
}
else {
source += regex.source;
}
}
return new RegExp(source, flags);
};
/**
* Generate the required regular expressions once.
*
* Regular Expressions are easier to manage this way and can be well described.
*
* @result {object} Object containing regular expressions.
*/
const __generateDefaultRegExpLib = () => {
// Note any variables with "Capture" in name include capturing bracket set(s).
const searchFlags = "gi", // ignore case for angles, "rgb" etc
rAngle = /(?:[+-]?\d*\.?\d+)(?:deg|grad|rad|turn)/, // Angle +ive, -ive and angle types
rSideCornerCapture = /to\s+((?:(?:left|right)(?:\s+(?:top|bottom))?))/, // optional 2nd part
rComma = /\s*,\s*/, // Allow space around comma.
rColorHex = /\#(?:[a-f0-9]{6}|[a-f0-9]{3})/, // 3 or 6 character form
rDigits3 = /\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}\s*\)/, // "(1, 2, 3)"
rDigits4 = /\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}\s*,\s*\d*\.?\d+\)/, // "(1, 2, 3, 4)"
rValue = /(?:[+-]?\d*\.?\d+)(?:%|[a-z]+)?/, // ".9", "-5px", "100%".
rKeyword = /[_a-z-][_a-z0-9-]*/, // "red", "transparent", "border-collapse".
rColor = __combineRegExp(["(?:", rColorHex, "|", "(?:rgb|hsl)", rDigits3, "|", "(?:rgba|hsla)", rDigits4, "|", rKeyword, ")"], ""), rColorStop = __combineRegExp([rColor, "(?:\\s+", rValue, "(?:\\s+", rValue, ")?)?"], ""), // Single Color Stop, optional %, optional length.
rColorStopList = __combineRegExp(["(?:", rColorStop, rComma, ")*", rColorStop], ""), // List of color stops min 1.
rLineCapture = __combineRegExp(["(?:(", rAngle, ")|", rSideCornerCapture, ")"], ""), // Angle or SideCorner
rGradientSearch = __combineRegExp(["(?:(", rLineCapture, ")", rComma, ")?(", rColorStopList, ")"], searchFlags), // Capture 1:"line", 2:"angle" (optional), 3:"side corner" (optional) and 4:"stop list".
rColorStopSearch = __combineRegExp(["\\s*(", rColor, ")", "(?:\\s+", "(", rValue, "))?", "(?:", rComma, "\\s*)?"], searchFlags); // Capture 1:"color" and 2:"position" (optional).
return {
gradientSearch: rGradientSearch,
colorStopSearch: rColorStopSearch
};
};
/**
* Actually parse the input gradient parameters string into an object for reusability.
*
*
* @note Really this only supports the standard syntax not historical versions, see MDN for details
* https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient
*
* @param {ColorGradientParserRegExpLib} regExpLib
* @param {string} input Input string in the form "to right bottom, #FF0 0%, red 20px, rgb(0, 0, 255) 100%"
* @returns {object|undefined} Object containing break down of input string including array of stop points.
*/
export var __parseGradient = (regExpLib, input) => {
var result;
var matchGradient, matchColorStop, stopResult;
// reset search position, because we reuse regex.
regExpLib.gradientSearch.lastIndex = 0;
matchGradient = regExpLib.gradientSearch.exec(input);
if (matchGradient !== null) {
result = {
original: matchGradient[0],
colorStopList: [],
line: null,
angle: null,
sideCorner: null
};
// Line (Angle or Side-Corner).
if (!!matchGradient[1]) {
result.line = matchGradient[1];
}
// Angle or undefined if side-corner.
if (!!matchGradient[2]) {
result.angle = matchGradient[2];
}
// Side-corner or undefined if angle.
if (!!matchGradient[3]) {
result.sideCorner = matchGradient[3];
}
// reset search position, because we reuse regex.
regExpLib.colorStopSearch.lastIndex = 0;
// Loop though all the color-stops.
matchColorStop = regExpLib.colorStopSearch.exec(matchGradient[4]);
while (matchColorStop !== null) {
stopResult = {
color: matchColorStop[1],
position: null
};
// Position (optional).
if (!!matchColorStop[2]) {
stopResult.position = matchColorStop[2];
}
result.colorStopList.push(stopResult);
// Continue searching from previous position.
matchColorStop = regExpLib.colorStopSearch.exec(matchGradient[4]);
}
}
// Can be undefined if match not found.
return result;
};
/**
* The actual parser class.
*/
export class LinearColorGradientParser {
constructor(regExpLib) {
this.regExpLib = regExpLib || __generateDefaultRegExpLib();
}
parse(input, positionConverter) {
const result = this.parseRaw(input);
return LinearColorGradientParser.parseResultToColorGradient(result, positionConverter);
}
parseRaw(input) {
var result;
const rGradientEnclosedInBrackets = /.*gradient\s*\(((?:\([^\)]*\)|[^\)\(]*)*)\)/; // Captures inside brackets - max one additional inner set.
const match = rGradientEnclosedInBrackets.exec(input);
const bracketPos = input.indexOf("(");
const gradientType = bracketPos === -1 ? null : input.substring(0, bracketPos).toLowerCase();
if (match !== null) {
// Get the parameters for the gradient
result = __parseGradient(this.regExpLib, match[1]);
result.gradientType = gradientType;
if (result.original.trim() !== match[1].trim()) {
// Did not match the input exactly - possible parsing error.
result.parseWarning = true;
}
}
else {
// result = new Error("Failed to find gradient");
throw new Error("Failed to find gradient");
}
return result;
}
static parseResultToColorGradient(result, positionConverter) {
if (!result.gradientType || result.gradientType.toLowerCase() != "linear-gradient") {
throw new Error(`Cannot create linear gradient from type '${result.gradientType}'.`);
}
if (!result.colorStopList || result.colorStopList.length <= 1) {
throw new Error(`Cannot create linear gradient from color stop list of length '${result.colorStopList.length}'. Too few elements.`);
}
const colorStops = [];
const converter = positionConverter !== null && positionConverter !== void 0 ? positionConverter : DefaultPositionConverter;
for (var i = 0; i < result.colorStopList.length; i++) {
const stopListItem = result.colorStopList[i];
console.log(stopListItem);
const color = Color.parse(stopListItem.color);
// var position: number = LinearColorGradientParser.parsePosition(stopListItem.position);
var ratio;
if (typeof stopListItem.position === "undefined") {
// Try to auto-fill undefined positions by their index in the list.
ratio = i / (result.colorStopList.length - 1);
}
else {
ratio = converter(stopListItem.position);
}
colorStops.push({ color: color, ratio: ratio });
}
return new ColorGradient(colorStops, result.line);
}
} // END class
// var test_this_one = function (regExpLib, input) {
// var result,
// rGradientEnclosedInBrackets = /.*gradient\s*\(((?:\([^\)]*\)|[^\)\(]*)*)\)/, // Captures inside brackets - max one additional inner set.
// match = rGradientEnclosedInBrackets.exec(input);
// if (match !== null) {
// // Get the parameters for the gradient
// result = parseGradient(regExpLib, match[1]);
// if (result.original.trim() !== match[1].trim()) {
// // Did not match the input exactly - possible parsing error.
// result.parseWarning = true;
// }
// } else {
// result = "Failed to find gradient";
// }
// return result;
// };
//# sourceMappingURL=LinearColorGradientParser.js.map