gradient-utils
Version:
css and svg gradient utils
294 lines (289 loc) • 9.07 kB
JavaScript
;
var utils = require('../utils-HrBEEUzj.js');
const R = 360;
/**
* @param angle - deg angle
* @param aspectRatio - width : height
* @param start - first stop color offset in percent
* @param end - last stop color offset in percent
*
* The SVG stop color offset cannot be less than 0 or greater than 100.
* (x1; y1) point takes into account the start offset
* (x2; y2) point takes into account the end offset
* Therefore, the converted color offsets must be on a scale from 0 to 100.
*/
const svgAngle = (angle, aspectRatio, start = 0, end = 100) => {
if (aspectRatio === 0 || !Number.isFinite(aspectRatio)) {
return {
x1: '0%',
y1: '0%',
x2: '0%',
y2: '0%',
};
}
start = start / 100;
end = end / 100;
if (end === start) {
end = start + 0.000001;
}
angle = ((angle % R) + R) % R;
const width = 300;
const height = width / aspectRatio;
const topQuadrant = angle > 90 && angle < 270;
const rightQuadrant = angle > 180 && angle < 360;
if (angle > 90 && angle < 180) {
angle = angle - 90;
}
else if (angle > 180 && angle < 270) {
angle = angle - 180;
}
else if (angle > 270 && angle < 360) {
angle = angle - 270;
}
const radAngle = (angle * Math.PI) / 180;
const DE = (0.5 * width - 0.5 * height * Math.tan(radAngle)) * Math.cos(radAngle);
const DF = DE * Math.sin(radAngle);
const DG = DE * Math.cos(radAngle);
const x1 = rightQuadrant ? width - DG : DG;
const y1 = topQuadrant ? -DF : height + DF;
const x2 = rightQuadrant ? DG : width - DG;
const y2 = topQuadrant ? height + DF : -DF;
let xs;
let ys;
let xe;
let ye;
if (start !== 0) {
if (start > 0) {
const k = start;
xs = x1 + k * (x2 - x1);
ys = y1 + k * (y2 - y1);
}
else {
const k = 1 - start;
xs = x2 + k * (x1 - x2);
ys = y2 + k * (y1 - y2);
}
}
else {
xs = x1;
ys = y1;
}
if (end !== 0) {
if (end > 1) {
const k = end;
xe = x1 + k * (x2 - x1);
ye = y1 + k * (y2 - y1);
}
else {
const k = 1 - end;
xe = x2 + k * (x1 - x2);
ye = y2 + k * (y1 - y2);
}
}
else {
xe = x2;
ye = y2;
}
return {
x1: `${(xs * 100) / width}%`,
y1: `${(ys * 100) / height}%`,
x2: `${(xe * 100) / width}%`,
y2: `${(ye * 100) / height}%`,
};
};
const prepareColorStops = (colorStops) => {
const preparedColorStops = [];
let start = 0;
let end = 100;
let min = 0;
for (let i = 0; i < colorStops.length; i++) {
const colorStop = checkSupportColorStop(colorStops[i]);
if (!colorStop) {
return null;
}
if (i === 0) {
if (!colorStop.start) {
colorStop.start = {
value: 0,
unit: '%',
sourceValue: '',
};
}
min = colorStop.start.value;
start = colorStop.start.value;
}
else if (colorStop.start) {
if (colorStop.start.value < min) {
colorStop.start.value = min;
}
else {
min = colorStop.start.value;
}
if (colorStop.end && colorStop.end.value < colorStop.start.value) {
colorStop.end = undefined;
}
}
if (i === colorStops.length - 1) {
if (!colorStop.start) {
colorStop.start = {
value: Math.max(min, 100),
unit: '%',
sourceValue: '',
};
}
colorStop.end = undefined;
end = colorStop.start.value;
}
preparedColorStops.push(colorStop);
}
return {
colorStops: preparedColorStops,
start,
end,
};
};
const svgColorStops = (colorStops, start = 0, end = 100) => {
var _a;
const offset = -start;
const multiplier = start === end ? 1 : 100 / Math.abs(end - start);
const result = [];
let undefinedIndex = -1;
let undefinedFound = false;
let resultIndex = 0;
for (let i = 0; i < colorStops.length; i++) {
const colorStop = colorStops[i];
const { color: stopColor, alpha: stopOpacity } = colorStop;
const cssOffset = (_a = colorStop.start) === null || _a === void 0 ? void 0 : _a.value;
if (cssOffset != null) {
const svgOffset = (cssOffset + offset) * multiplier;
if (undefinedFound) {
fillEqualOffset(result, undefinedIndex, svgOffset);
undefinedFound = false;
}
result.push({
stopColor,
stopOpacity,
offset: svgOffset,
});
}
else {
result.push({
stopColor,
stopOpacity,
offset: undefined,
});
if (!undefinedFound) {
undefinedFound = true;
undefinedIndex = resultIndex;
}
}
resultIndex++;
if (colorStop.end) {
result.push({
stopColor,
stopOpacity,
offset: (colorStop.end.value + offset) * multiplier,
});
resultIndex++;
}
}
return result;
};
const fillEqualOffset = (colorStops, from, toOffset) => {
var _a, _b;
const prevOffset = (_b = (_a = colorStops[from - 1]) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : 0;
const leftSpace = toOffset - prevOffset;
const items = colorStops.length - from + 1;
const length = leftSpace / items;
for (let i = from; i < colorStops.length; i++) {
colorStops[i].offset = (colorStops[i - 1].offset || 0) + length;
}
};
const checkSupportColorStop = (colorStop) => {
if (!colorStop) {
console.warn('should be at least two color stops');
return null;
}
if (!isColorStop(colorStop)) {
console.warn("don't support ColorHint");
return null;
}
if (colorStop.start && colorStop.start.unit !== '%' && colorStop.start.unit !== '') {
console.warn(`unsupported offset unit ${colorStop.start.unit} (${colorStop.start.sourceValue})`);
return null;
}
if (colorStop.end && colorStop.end.unit !== '%' && colorStop.end.unit !== '') {
console.warn(`unsupported offset end unit ${colorStop.end.unit} (${colorStop.end.sourceValue})`);
return null;
}
return Object.assign({}, colorStop);
};
const isColorStop = (colorStop) => 'color' in colorStop;
const radToDeg = (rad) => rad * (180 / Math.PI);
const convertLinearGradient = (cssGradient, aspectRatio) => {
if (cssGradient.repeatable) {
console.warn("don't support repeatable gradient");
return null;
}
if (utils.isLinearGradientWithInterpolation(cssGradient)) {
console.warn("don't support gradient with interpolation");
return null;
}
let degAngle;
if (utils.isLinearGradientWithAngle(cssGradient)) {
degAngle = cssGradient.angle.degAngle;
}
else {
const { top, bottom, left, right } = cssGradient.sideOrCorner;
if (top) {
if (left) {
//to top left
degAngle = 270 + radToDeg(Math.atan(aspectRatio));
}
else if (right) {
//to top right
degAngle = 90 - radToDeg(Math.atan(aspectRatio));
}
else {
//to top
degAngle = 0;
}
}
else if (bottom) {
if (left) {
//to bottom left
degAngle = 270 - radToDeg(Math.atan(aspectRatio));
}
else if (right) {
//to bottom right
degAngle = 90 + radToDeg(Math.atan(aspectRatio));
}
else {
//to bottom
degAngle = 180;
}
}
else if (left) {
//to left
degAngle = 270;
}
else {
//to right
degAngle = 90;
}
}
const prepareResult = prepareColorStops(cssGradient.colorStops);
if (!prepareResult) {
return null;
}
const { colorStops: rawColorStops, start, end } = prepareResult;
const result = Object.assign(Object.assign({}, svgAngle(degAngle, aspectRatio, start, end)), { colorStops: [] });
const colorStops = svgColorStops(rawColorStops, start, end);
if (colorStops === null) {
console.warn("Can't get color stops");
return null;
}
result.colorStops = colorStops;
return result;
};
exports.convertLinearGradient = convertLinearGradient;