UNPKG

zeplin-extension-style-kit

Version:

Models and utilities to generate CSS-like style code in Zeplin extensions.

162 lines (133 loc) 4.87 kB
import { ColorStop } from "@zeplin/extension-model"; import { ColorNameResolver, ColorParams, StyleValue } from "../../common.js"; import { LinearColorStop } from "./linearColorStop.js"; interface Point { x: number; y: number; } export class LinearGradient implements StyleValue { angle: number; colorStops: LinearColorStop[]; constructor( colorStops: ColorStop[], from: Point, to: Point, width: number, height: number ) { /* * Convert the relative `from` and `to` positions on a layer to absolute positions * by multiplying them with the width and height of the layer */ const x1 = from.x * width; const y1 = from.y * height; const x2 = to.x * width; const y2 = to.y * height; this.angle = (360 - Math.round(Math.atan2(x1 - x2, y1 - y2) * 180 / Math.PI)) % 360; let pFirst: number; let pLast: number; let pStart: number; let pEnd: number; // Find the slope of the gradient line const m1 = (y2 - y1) / (x2 - x1); if (m1 === 0) { // If the slope is 0, gradient line is horizontal pFirst = x1; pLast = x2; pStart = 0; pEnd = width; } else if (!Number.isFinite(m1)) { // If the slope is infinite, gradient line is vertical pFirst = y1; pLast = y2; pStart = 0; pEnd = height; } else { pFirst = x1; pLast = x2; // Find c1 such that y = m1 * x + c1, by applying one of the positions const c1 = y1 - m1 * x1; // The slope of a perpendicular line const m2 = -1 / m1; // Starting and ending points let x0; let y0; let xe; let ye; if (m1 > 0) { /* * If the slope is positive, starting point is top-left corner (0, 0) * and ending point is bottom-right corner (w, h) */ x0 = 0; y0 = 0; xe = width; ye = height; } else { /* * If the slope is negative, starting point is bottom-left corner (0, h) * and ending point is top-right corner (w, 0) */ x0 = 0; y0 = height; xe = width; ye = 0; } /* * `c2` equation constant of the line crossing from starting point * and perpendicular to the gradient line */ const c2 = y0 - m2 * x0; /* * `x` position of the intersection point of gradient line * and the perpendecular line crossing from starting point */ pStart = (c2 - c1) / (m1 - m2); /* * `c3` equation constant of the line crossing from ending point * and perpendicular to the gradient line */ const c3 = ye - m2 * xe; /* * `x` position of the intersection point of gradient line * and the perpendecular line crossing from ending point */ pEnd = (c3 - c1) / (m1 - m2); } this.colorStops = colorStops.map(({ color, position: p }) => { // Convert the position coming from design tool to position on the actual gradient line const position = (p * (pLast - pFirst) + pFirst - pStart) / (pEnd - pStart); return new LinearColorStop({ color, position }); }); } valueOf(): string { const { angle, colorStops } = this; return `linearGradient::a:${angle}:${colorStops.map(cs => cs.valueOf()).join(":")}`; } equals(other: LinearGradient): boolean { return ( this.angle === other.angle && this.colorStops.length === other.colorStops.length && this.colorStops.every((cs, index) => cs.equals(other.colorStops[index])) ); } toCSSAngle(angle: number) { switch (angle) { case 0: return "to top"; case 90: return "to right"; case 180: return "to bottom"; case 270: return "to left"; default: return `${angle}deg`; } } toStyleValue({ colorFormat }: ColorParams, colorNameResolver: ColorNameResolver) { const { angle, colorStops } = this; const colorStopStyle = colorStops.map(cs => cs.toStyleValue({ colorFormat }, colorNameResolver)).join(", "); return `linear-gradient(${this.toCSSAngle(angle)}, ${colorStopStyle})`; } }