From 8822129dafbafb4d349076838babea9053504bb8 Mon Sep 17 00:00:00 2001 From: macintoshhelper <6757532+macintoshhelper@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:32:15 +0300 Subject: [PATCH 1/6] fix: align linear gradient start/stop transform to rectangle edges for custom angles --- .../src/plugin/figmaTransforms/gradients.ts | 60 ++++++++++--------- .../src/plugin/setColorValuesOnTarget.ts | 2 +- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts index f7bea29f7..2b8ccd764 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts @@ -17,7 +17,7 @@ export function convertFigmaGradientToString(paint: GradientPaint) { return `linear-gradient(${angleInDeg + 90}deg, ${gradientStopsString})`; } -export function convertStringToFigmaGradient(value: string) { +export function convertStringToFigmaGradient(value: string, node?: BaseNode | PaintStyle) { const parts = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')).split(', ').map(s => s.trim()); // Default angle is to top (180 degrees) @@ -63,39 +63,41 @@ export function convertStringToFigmaGradient(value: string) { const degrees = -(angle - 90); const rad = degrees * (Math.PI / 180); - const scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); - // start by transforming to the gradient center + let normalizedAngleRad = Math.abs(rad) % (Math.PI / 2); + if (normalizedAngleRad > Math.PI / 4) { + // adjust angle after 45 degrees to scale down correctly towards 90 degrees + normalizedAngleRad = Math.PI / 2 - normalizedAngleRad; + } + + const sin = Math.sin(rad); + const cos = Math.cos(rad); + + let scale = 1; + + if (['RECTANGLE', 'FRAME'].includes(node?.type || '')) { + const normalisedCos = Math.cos(normalizedAngleRad); + scale = normalisedCos; + } else { + // FIXME: fallback, but might break paintStyleMatchesColorToken + scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); + } + + const scaledCos = cos * scale; + const scaledSin = sin * scale; + + // start by transforming to the gradient center, to keep the gradient centered after scaling // which for figma is .5 .5 as it is a relative transform + const tx = 0.5 - 0.5 * scaledCos + 0.5 * scaledSin; + const ty = 0.5 - 0.5 * scaledSin - 0.5 * scaledCos; + const transformationMatrix = new Matrix([ - [1, 0, 0.5], - [0, 1, 0.5], + [scaledCos, -scaledSin, tx], + [scaledSin, scaledCos, ty], [0, 0, 1], - ]).mmul( - // we can then multiply this with the rotation matrix - new Matrix([ - [Math.cos(rad), Math.sin(rad), 0], - [-Math.sin(rad), Math.cos(rad), 0], - [0, 0, 1], - ]), - ).mmul( - // next we need to multiply it with a scale matrix to fill the entire shape - new Matrix([ - [scale, 0, 0], - [0, scale, 0], - [0, 0, 1], - ]), - ).mmul( - // lastly we need to translate it back to the 0,0 origin - // by negating the center transform - new Matrix([ - [1, 0, -0.5], - [0, 1, -0.5], - [0, 0, 1], - ]), - ); + ]); - const gradientTransformMatrix = inverse(transformationMatrix).to2DArray(); + const gradientTransformMatrix = transformationMatrix.to2DArray(); const gradientStops = parts.map((stop, i, arr) => { const seperatedStop = stop.split(' '); diff --git a/packages/tokens-studio-for-figma/src/plugin/setColorValuesOnTarget.ts b/packages/tokens-studio-for-figma/src/plugin/setColorValuesOnTarget.ts index edd1f593e..553539255 100644 --- a/packages/tokens-studio-for-figma/src/plugin/setColorValuesOnTarget.ts +++ b/packages/tokens-studio-for-figma/src/plugin/setColorValuesOnTarget.ts @@ -34,7 +34,7 @@ export default async function setColorValuesOnTarget({ if (resolvedValue.startsWith?.('linear-gradient')) { const fallbackValue = defaultTokenValueRetriever.get(token)?.value; - const { gradientStops, gradientTransform } = convertStringToFigmaGradient(fallbackValue); + const { gradientStops, gradientTransform } = convertStringToFigmaGradient(fallbackValue, target); const rawValue = defaultTokenValueRetriever.get(token)?.rawValue; let gradientStopsWithReferences = gradientStops; From 50388f1abcc881684b77916fa7cc6a53fec3feee Mon Sep 17 00:00:00 2001 From: macintoshhelper <6757532+macintoshhelper@users.noreply.github.com> Date: Wed, 4 Sep 2024 19:15:03 +0300 Subject: [PATCH 2/6] fix: remove linear gradient node type check for isPaintEqual --- .../plugin/figmaTransforms/gradients.test.ts | 52 +++++++++++++++---- .../src/plugin/figmaTransforms/gradients.ts | 23 ++++---- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts index 7c34f2f4b..45c3cb856 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts @@ -25,8 +25,8 @@ describe('convertStringtoFigmaGradient', () => { }, ], gradientTransform: [ - [0.5000000000000001, -0.5, 0.49999999999999994], - [0.5, 0.5000000000000001, -5.551115123125784e-17], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0], ], }, }; @@ -64,8 +64,8 @@ describe('convertStringtoFigmaGradient', () => { }, ], gradientTransform: [ - [0.5000000000000001, -0.5, 0.49999999999999994], - [0.5, 0.5000000000000001, -5.551115123125784e-17], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0], ], }, }; @@ -103,8 +103,8 @@ describe('convertStringtoFigmaGradient', () => { }, ], gradientTransform: [ - [0.5000000000000001, -0.5, 0.49999999999999994], - [0.5, 0.5000000000000001, -5.551115123125784e-17], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0], ], }, }; @@ -133,8 +133,8 @@ describe('convertStringtoFigmaGradient', () => { } ], gradientTransform: [ - [6.123233995736766e-17, 1, -6.123233995736766e-17], - [-1, 6.123233995736766e-17, 1] + [0, 1, 0], + [-1, 0, 1] ] } }; @@ -190,8 +190,38 @@ describe('convertStringtoFigmaGradient', () => { } ], gradientTransform: [ - [6.123233995736766e-17, 1, -6.123233995736766e-17], - [-1, 6.123233995736766e-17, 1] + [0, 1, 0], + [-1, 0, 1] + ] + } + } + + const test7 = { + input: 'linear-gradient(106.84deg, #FF0000 5.61%, #cc00ff00 89.41%)', + output: { + gradientStops: [ + { + color: { + r: 1, + g: 0, + b: 0, + a: 1 + }, + position: 0.056100000000000004, + }, + { + color: { + r: 0.8, + g: 0, + b: 1, + a: 0, + }, + position: 0.8941 + } + ], + gradientTransform: [ + [0.9160738743, 0.2772769934, -0.0966754339], + [-0.2772769934, 0.9160738743, 0.1806015595], ] } } @@ -201,7 +231,7 @@ describe('convertStringtoFigmaGradient', () => { expect(convertStringToFigmaGradient(test3.input)).toEqual(test3.output); expect(convertStringToFigmaGradient(test4.input)).toEqual(test4.output); expect(convertStringToFigmaGradient(test5.input)).toEqual(test5.output); - expect(convertStringToFigmaGradient(test6.input)).toEqual(test6.output); + expect(convertStringToFigmaGradient(test7.input)).toEqual(test7.output); }); describe('convertFigmaGradientToString', () => { diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts index 2b8ccd764..e9379a280 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts @@ -17,6 +17,11 @@ export function convertFigmaGradientToString(paint: GradientPaint) { return `linear-gradient(${angleInDeg + 90}deg, ${gradientStopsString})`; } +const roundToPrecision = (value, precision = 10) => { + const roundToPrecision = 10**precision; + return Math.round((value + Number.EPSILON) * roundToPrecision) / roundToPrecision; +} + export function convertStringToFigmaGradient(value: string, node?: BaseNode | PaintStyle) { const parts = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')).split(', ').map(s => s.trim()); @@ -75,13 +80,13 @@ export function convertStringToFigmaGradient(value: string, node?: BaseNode | Pa let scale = 1; - if (['RECTANGLE', 'FRAME'].includes(node?.type || '')) { - const normalisedCos = Math.cos(normalizedAngleRad); - scale = normalisedCos; - } else { - // FIXME: fallback, but might break paintStyleMatchesColorToken - scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); - } + const normalisedCos = Math.cos(normalizedAngleRad); + scale = normalisedCos; + // Implement fallback if bugs are caused by obscure node types. This appears to be unnecessary + // if (!['RECTANGLE', 'FRAME', 'VECTOR'].includes(node?.type || '')) { + // Old scale computation: + // scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); + // } const scaledCos = cos * scale; const scaledSin = sin * scale; @@ -92,8 +97,8 @@ export function convertStringToFigmaGradient(value: string, node?: BaseNode | Pa const ty = 0.5 - 0.5 * scaledSin - 0.5 * scaledCos; const transformationMatrix = new Matrix([ - [scaledCos, -scaledSin, tx], - [scaledSin, scaledCos, ty], + [roundToPrecision(scaledCos), roundToPrecision(-scaledSin), roundToPrecision(tx)], + [roundToPrecision(scaledSin), roundToPrecision(scaledCos), roundToPrecision(ty)], [0, 0, 1], ]); From 635ec0f89382bf26c28cd2e0284b1c8cd5caa79a Mon Sep 17 00:00:00 2001 From: macintoshhelper <6757532+macintoshhelper@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:28:27 +0400 Subject: [PATCH 3/6] fix: lint errors --- .../src/plugin/figmaTransforms/gradients.test.ts | 16 ++++++++-------- .../src/plugin/figmaTransforms/gradients.ts | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts index 45c3cb856..dc9bd5e57 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts @@ -134,8 +134,8 @@ describe('convertStringtoFigmaGradient', () => { ], gradientTransform: [ [0, 1, 0], - [-1, 0, 1] - ] + [-1, 0, 1], + ], } }; @@ -191,9 +191,9 @@ describe('convertStringtoFigmaGradient', () => { ], gradientTransform: [ [0, 1, 0], - [-1, 0, 1] - ] - } + [-1, 0, 1], + ], + }, } const test7 = { @@ -216,13 +216,13 @@ describe('convertStringtoFigmaGradient', () => { b: 1, a: 0, }, - position: 0.8941 - } + position: 0.8941, + }, ], gradientTransform: [ [0.9160738743, 0.2772769934, -0.0966754339], [-0.2772769934, 0.9160738743, 0.1806015595], - ] + ], } } diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts index e9379a280..c81157570 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts @@ -1,5 +1,5 @@ import { figmaRGBToHex, extractLinearGradientParamsFromTransform } from '@figma-plugin/helpers'; -import { Matrix, inverse } from 'ml-matrix'; +import { Matrix } from 'ml-matrix'; import { convertToFigmaColor } from './colors'; export function convertDegreeToNumber(degreeString: string): number { @@ -18,11 +18,11 @@ export function convertFigmaGradientToString(paint: GradientPaint) { } const roundToPrecision = (value, precision = 10) => { - const roundToPrecision = 10**precision; - return Math.round((value + Number.EPSILON) * roundToPrecision) / roundToPrecision; + const roundToPrecisionVal = 10 ** precision; + return Math.round((value + Number.EPSILON) * roundToPrecisionVal) / roundToPrecisionVal; } -export function convertStringToFigmaGradient(value: string, node?: BaseNode | PaintStyle) { +export function convertStringToFigmaGradient(value: string/*, node?: BaseNode | PaintStyle*/) { const parts = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')).split(', ').map(s => s.trim()); // Default angle is to top (180 degrees) @@ -84,8 +84,8 @@ export function convertStringToFigmaGradient(value: string, node?: BaseNode | Pa scale = normalisedCos; // Implement fallback if bugs are caused by obscure node types. This appears to be unnecessary // if (!['RECTANGLE', 'FRAME', 'VECTOR'].includes(node?.type || '')) { - // Old scale computation: - // scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); + // // Old scale computation: + // scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); // } const scaledCos = cos * scale; From b3cf758e105b77bb3d1e243758494d8437dc214a Mon Sep 17 00:00:00 2001 From: macintoshhelper <6757532+macintoshhelper@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:41:50 +0400 Subject: [PATCH 4/6] fix: further lint errors --- .../src/plugin/figmaTransforms/gradients.test.ts | 2 +- .../src/plugin/figmaTransforms/gradients.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts index dc9bd5e57..9bbca00e3 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts @@ -205,7 +205,7 @@ describe('convertStringtoFigmaGradient', () => { r: 1, g: 0, b: 0, - a: 1 + a: 1, }, position: 0.056100000000000004, }, diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts index c81157570..38f2f1aec 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.ts @@ -20,9 +20,10 @@ export function convertFigmaGradientToString(paint: GradientPaint) { const roundToPrecision = (value, precision = 10) => { const roundToPrecisionVal = 10 ** precision; return Math.round((value + Number.EPSILON) * roundToPrecisionVal) / roundToPrecisionVal; -} +}; -export function convertStringToFigmaGradient(value: string/*, node?: BaseNode | PaintStyle*/) { +// if node type check is needed due to bugs caused by obscure node types, use (value: string/*, node?: BaseNode | PaintStyle) and convertStringToFigmaGradient(value, target) +export function convertStringToFigmaGradient(value: string) { const parts = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')).split(', ').map(s => s.trim()); // Default angle is to top (180 degrees) @@ -84,7 +85,7 @@ export function convertStringToFigmaGradient(value: string/*, node?: BaseNode | scale = normalisedCos; // Implement fallback if bugs are caused by obscure node types. This appears to be unnecessary // if (!['RECTANGLE', 'FRAME', 'VECTOR'].includes(node?.type || '')) { - // // Old scale computation: + // // Old scale computation: // scale = angle % 90 === 0 ? 1 : Math.sqrt(1 + Math.tan(angle * (Math.PI / 180)) ** 2); // } From 982c172e71ee0f6efcd09f02ee635b57927fa32c Mon Sep 17 00:00:00 2001 From: macintoshhelper <6757532+macintoshhelper@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:52:51 +0400 Subject: [PATCH 5/6] fix: add missing test back to gradients.test --- .../src/plugin/figmaTransforms/gradients.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts index 9bbca00e3..dddaf1a42 100644 --- a/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts +++ b/packages/tokens-studio-for-figma/src/plugin/figmaTransforms/gradients.test.ts @@ -194,7 +194,7 @@ describe('convertStringtoFigmaGradient', () => { [-1, 0, 1], ], }, - } + }; const test7 = { input: 'linear-gradient(106.84deg, #FF0000 5.61%, #cc00ff00 89.41%)', @@ -231,6 +231,7 @@ describe('convertStringtoFigmaGradient', () => { expect(convertStringToFigmaGradient(test3.input)).toEqual(test3.output); expect(convertStringToFigmaGradient(test4.input)).toEqual(test4.output); expect(convertStringToFigmaGradient(test5.input)).toEqual(test5.output); + expect(convertStringToFigmaGradient(test6.input)).toEqual(test6.output); expect(convertStringToFigmaGradient(test7.input)).toEqual(test7.output); }); From a7c56617c5c2be94e6aecf56f7b4715e7466ba98 Mon Sep 17 00:00:00 2001 From: macintoshhelper Date: Tue, 15 Oct 2024 14:02:38 +0300 Subject: [PATCH 6/6] Create clever-turtles-fry.md --- .changeset/clever-turtles-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clever-turtles-fry.md diff --git a/.changeset/clever-turtles-fry.md b/.changeset/clever-turtles-fry.md new file mode 100644 index 000000000..4144c11e7 --- /dev/null +++ b/.changeset/clever-turtles-fry.md @@ -0,0 +1,5 @@ +--- +"@tokens-studio/figma-plugin": patch +--- + +Fix for linear gradient start/end points being outside the node bounding box when using angles that aren't divisible by 45.