UNPKG

@bacons/css-to-expo-linear-gradient

Version:

Convert a CSS linear gradient function to expo-linear-gradient props

213 lines 8.04 kB
"use strict"; // From https://github.com/niklasvh/html2canvas/tree/eeda86bd5e81fb4e97675fe9bee3d4d15899997f Object.defineProperty(exports, "__esModule", { value: true }); exports.fromCSS = void 0; const ANGLE = /([+-]?\d*\.?\d+)(deg|grad|rad|turn)/i; const SIDE_OR_CORNER = /^(to )?(left|top|right|bottom)( (left|top|right|bottom))?$/i; const PERCENTAGE_ANGLES = /^([+-]?\d*\.?\d+)% ([+-]?\d*\.?\d+)%$/i; const ENDS_WITH_LENGTH = /(px)$|(%$)|( 0$)/; const FROM_TO_COLORSTOP = /^(from|to|color-stop)\((?:([\d.]+)(%)?,\s*)?(.+?)\)$/i; const parseAngle = (angle) => { const match = angle.match(ANGLE); if (match) { const value = parseFloat(match[1]); switch (match[2].toLowerCase()) { case "deg": return (Math.PI * value) / 180; case "grad": return (Math.PI / 200) * value; case "rad": return value; case "turn": return Math.PI * 2 * value; } } return null; }; const distance = (a, b) => Math.sqrt(a * a + b * b); const parseGradient = ({ args, method, prefix, }, bounds) => { if (method === "linear-gradient") { return parseLinearGradient(args, bounds, !!prefix); } else if (method === "gradient" && args[0] === "linear") { // TODO handle correct angle return parseLinearGradient(["to bottom"].concat(transformObsoleteColorStops(args.slice(3))), bounds, !!prefix); } else if (method === "radial-gradient" || (method === "gradient" && args[0] === "radial")) { throw new Error("radial gradients are not supported"); } throw new Error("unknown gradient type: " + method); }; const parseColorStops = (args, firstColorStopIndex) => { const colorStops = []; for (let i = firstColorStopIndex; i < args.length; i++) { const value = args[i]; const HAS_LENGTH = ENDS_WITH_LENGTH.test(value); const lastSpaceIndex = value.lastIndexOf(" "); const color = HAS_LENGTH ? value.substring(0, lastSpaceIndex) : value; const stop = HAS_LENGTH ? value.substring(lastSpaceIndex + 1) : i === firstColorStopIndex ? "0%" : i === args.length - 1 ? "100%" : // fallback to evenly spaced color stops // prettier-ignore `${((i - firstColorStopIndex) / (args.length - firstColorStopIndex - 1)) * 100}%`; colorStops.push({ color, stop }); } const parsePosition = (p) => { const percentage = parseInt(p.replace(/%$/, ""), 10); const normal = percentage / 100; return normal; }; const colors = []; const locations = []; colorStops.forEach(({ color, stop }) => { colors.push(color); locations.push(parsePosition(stop)); }); colors.reverse(); return { colors, locations }; }; const parseLinearGradient = (args, bounds, hasPrefix) => { const angle = parseAngle(args[0]); const HAS_SIDE_OR_CORNER = SIDE_OR_CORNER.test(args[0]); const HAS_DIRECTION = HAS_SIDE_OR_CORNER || angle !== null || PERCENTAGE_ANGLES.test(args[0]); const direction = HAS_DIRECTION ? angle !== null ? calculateGradientDirection( // if there is a prefix, the 0° angle points due East (instead of North per W3C) hasPrefix ? angle - Math.PI * 0.5 : angle, bounds) : HAS_SIDE_OR_CORNER ? parseSideOrCorner(args[0], bounds) : parsePercentageAngle(args[0], bounds) : calculateGradientDirection(Math.PI, bounds); const firstColorStopIndex = HAS_DIRECTION ? 1 : 0; return { ...parseColorStops(args, firstColorStopIndex), start: { x: direction.x0, y: direction.y0 }, end: { x: direction.x1, y: direction.y1 }, }; }; const calculateGradientDirection = (radian, bounds) => { const width = bounds.width; const height = bounds.height; const HALF_WIDTH = width * 0.5; const HALF_HEIGHT = height * 0.5; const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian)); const HALF_LINE_LENGTH = lineLength / 2; const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH; const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH; const x1 = width - x0; const y1 = height - y0; return { x0, x1, y0, y1 }; }; const parseTopRight = (bounds) => Math.acos(bounds.width / 2 / (distance(bounds.width, bounds.height) / 2)); const parseSideOrCorner = (side, bounds) => { switch (side) { case "bottom": case "to top": return calculateGradientDirection(0, bounds); case "left": case "to right": return calculateGradientDirection(Math.PI / 2, bounds); case "right": case "to left": return calculateGradientDirection((3 * Math.PI) / 2, bounds); case "top right": case "right top": case "to bottom left": case "to left bottom": return calculateGradientDirection(Math.PI + parseTopRight(bounds), bounds); case "top left": case "left top": case "to bottom right": case "to right bottom": return calculateGradientDirection(Math.PI - parseTopRight(bounds), bounds); case "bottom left": case "left bottom": case "to top right": case "to right top": return calculateGradientDirection(parseTopRight(bounds), bounds); case "bottom right": case "right bottom": case "to top left": case "to left top": return calculateGradientDirection(2 * Math.PI - parseTopRight(bounds), bounds); case "top": case "to bottom": default: return calculateGradientDirection(Math.PI, bounds); } }; const parsePercentageAngle = (angle, bounds) => { const [left, top] = angle.split(" ").map(parseFloat); const ratio = ((left / 100) * bounds.width) / ((top / 100) * bounds.height); return calculateGradientDirection(Math.atan(isNaN(ratio) ? 1 : ratio) + Math.PI / 2, bounds); }; const transformObsoleteColorStops = (args) => { // @ts-expect-error return (args .map((color) => color.match(FROM_TO_COLORSTOP)) // @ts-expect-error .map((v, index) => { if (!v) { return args[index]; } switch (v[1]) { case "from": return `${v[4]} 0%`; case "to": return `${v[4]} 100%`; case "color-stop": if (v[3] === "%") { return `${v[4]} ${v[2]}`; } return `${v[4]} ${parseFloat(v[2]) * 100}%`; } })); }; /** * Given a CSS string, returns props for rendering with `expo-linear-gradient`. * * ```tsx * fromCSS('linear-gradient(180deg, #ff008450 0%, #fca40040 25%, #ffff0030 40%, #00ff8a20 60%, #00cfff40 75%, #cc4cfa50 100%);') * ``` */ function fromCSS(str) { if (str.endsWith(";")) { str = str.slice(0, -1); } const values = str === null || str === void 0 ? void 0 : str.match(/([a-zA-Z0-9_-]+)\((.*)\).*/); if (!values) throw new Error("Invalid CSS Gradient function: " + str); const [, method, argString] = values; const args = []; let buffer = ""; let openParens = 0; for (let i = 0; i < argString.length; i++) { const char = argString[i]; if (char === "," && openParens === 0) { args.push(buffer.trim()); buffer = ""; } else { buffer += char; if (char === "(") { openParens++; } else if (char === ")") { openParens--; } } } args.push(buffer.trim()); return parseGradient({ args, method, }, { width: 1, height: 1 }); } exports.fromCSS = fromCSS; //# sourceMappingURL=index.js.map