Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor: Add support for applying gradient to text #13590

Merged
merged 34 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9d03253
Add textColor attribute to TextElement
Swanand01 Feb 16, 2024
734bd44
Add `generateTextColorCSS` for generating CSS styles from textColor
Swanand01 Feb 16, 2024
ecf99c0
Get color using `useCommonColorValue` and use `pushUpdate` instead of…
Swanand01 Feb 16, 2024
81328b3
Generate and apply textColor styles to `EditTextBox`
Swanand01 Feb 16, 2024
9246668
Remove `useColorTransformHandler` for `color` property.
Swanand01 Feb 16, 2024
57f6e90
Merge branch 'main' into feat/12143-apply-gradient-to-text
AnuragVasanwala Feb 17, 2024
9562520
⏪ Revert the added `textColor` attribute
Swanand01 Feb 22, 2024
861e8ad
⏪ Revert the added `generateTextColorCSS` function
Swanand01 Feb 22, 2024
d9df571
⏪ Revert the used `textColor` attribute
Swanand01 Feb 22, 2024
b03db75
Add rich-text formatter (getter and setter) for gradient color
Swanand01 Feb 22, 2024
cbb9ce0
Add `getColorFromGradientStyle` and `getGradientStyleFromColor` utili…
Swanand01 Feb 22, 2024
987ad71
⏪ Revert the used `textColor` attribute in `edit.js`
Swanand01 Feb 22, 2024
eec3e96
♻️ Refactor to use the gradientColor formatters
Swanand01 Feb 22, 2024
5a6aa48
Merge branch 'feat/12143-apply-gradient-to-text' of https://github.co…
Swanand01 Feb 22, 2024
09ba44d
Update regex for rotation in linear gradient
Swanand01 Mar 4, 2024
e587b08
♻️ Remove use of shorthand css styles
Swanand01 Mar 4, 2024
6ad55fa
Unset WebkitTextFillColor on selecting text
Swanand01 Mar 4, 2024
9e0b841
Refine rotation regex for linear gradient
Swanand01 Mar 4, 2024
3e5fa35
Export and use `DEFAULT_GRADIENT`
Swanand01 Mar 6, 2024
ebe42f8
Merge branch 'main' into feat/12143-apply-gradient-to-text
AnuragVasanwala Mar 6, 2024
387d45d
Fix: Show `Mixed` in color input on selecting two text elements with …
Swanand01 Mar 18, 2024
0edb225
Fix redundant imports
Swanand01 Mar 18, 2024
b918943
Use `DEFAULT_GRADIENT` in `textColor.js`
Swanand01 Mar 18, 2024
ac339b3
Add unit tests
Swanand01 Mar 18, 2024
5ad437d
Add gradient button getters to `ColorPicker` container
Swanand01 Mar 18, 2024
55e14a8
Add karma tests
Swanand01 Mar 18, 2024
af4583f
Fix linting issues
Swanand01 Mar 18, 2024
9b69f1d
Merge branch 'main' into feat/12143-apply-gradient-to-text
Swanand01 Mar 18, 2024
06e0660
Move `DEFAULT_GRADIENT`, `GRADIENT` and `GRADIENT_REGEX` to `constant…
Swanand01 Mar 19, 2024
a949b4a
Fix order of styles in `stylesToCSS`
Swanand01 Mar 19, 2024
8cb55a8
Throw error if invalid style string is passed to `getColorFromGradien…
Swanand01 Mar 19, 2024
4610988
Add unit tests for `getColorFromGradientStyle`
Swanand01 Mar 19, 2024
64710d2
Update the order of styles in unit tests and karma tests
Swanand01 Mar 19, 2024
413b47d
Merge branch 'feat/12143-apply-gradient-to-text' of https://github.co…
Swanand01 Mar 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/element-library/src/text/display.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ function TextDisplay({
targetRef: bgRef,
expectedStyle: 'background',
});
useColorTransformHandler({ id, targetRef: fgRef, expectedStyle: 'color' });
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
useColorTransformHandler({
id,
targetRef: refWithBorder,
Expand Down
5 changes: 2 additions & 3 deletions packages/element-library/src/text/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@
if (verticalPadding === 0) {
return `${lineHeight}em`;
}
return `calc(${lineHeight}em ${verticalPadding > 0 ? '+' : '-'} ${
2 * Math.abs(verticalPadding)
}${unit})`;
return `calc(${lineHeight}em ${verticalPadding > 0 ? '+' : '-'} ${2 * Math.abs(verticalPadding)

Check failure on line 111 in packages/element-library/src/text/util.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/element-library/src/text/util.ts#L111

[prettier/prettier] Insert `⏎····`
}${unit})`;

Check failure on line 112 in packages/element-library/src/text/util.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/element-library/src/text/util.ts#L112

[prettier/prettier] Delete `··`
};

export function calcFontMetrics(element: TextElement) {
Expand Down
126 changes: 126 additions & 0 deletions packages/patterns/src/getColorFromGradientStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Internal dependencies
*/
import createSolidFromString from './createSolidFromString';
import { PatternType, type Gradient, type Linear } from './types';

const DEFAULT_GRADIENT: Linear = {
type: PatternType.Linear,
stops: [
{
color: {
r: 0,
g: 0,
b: 0,
},
position: 0,
},
{
color: {
r: 0,
g: 0,
b: 0,
},
position: 1,
},
],
};

const GRADIENT = {
LINEAR: 'linear-gradient',
RADIAL: 'radial-gradient',
};

const REGEX = {
[GRADIENT.LINEAR]:
/linear-gradient\((?:-?\d*\.?\d+turn,\s*)?(?:rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),?\s*(\d*\.?\d+)?\)|#([0-9a-fA-F]{6}))\s*0%,\s*(?:rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),?\s*(\d*\.?\d+)?\)|#([0-9a-fA-F]{6}))\s*100%\)/,
[GRADIENT.RADIAL]:
/radial-gradient\((?:rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),?\s*(\d*\.?\d+)?\)|#([0-9a-fA-F]{6}))\s*0%,\s*(?:rgba?\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),?\s*(\d*\.?\d+)?\)|#([0-9a-fA-F]{6}))\s*100%\)/,
};

export default function getColorFromGradientStyle(style: string): Gradient {
Swanand01 marked this conversation as resolved.
Show resolved Hide resolved
if (style.includes(GRADIENT.LINEAR)) {
return parseGradient(style, GRADIENT.LINEAR);
}
return parseGradient(style, GRADIENT.RADIAL);
}

function parseGradient(style: string, gradient: string): Gradient {
const regex = REGEX[gradient];
const matches = style.match(regex);
if (!matches) {
return DEFAULT_GRADIENT;
}

const { startColor, endColor } = getColorRange(matches);

if (gradient === GRADIENT.LINEAR) {
const rotationMatch = style.match(/-?\d*\.?\d+turn/);
Fixed Show fixed Hide fixed
return {
type: PatternType.Linear,
stops: [
{ color: startColor, position: 0 },
{ color: endColor, position: 1 },
],
rotation: rotationMatch ? parseFloat(rotationMatch[0]) : 0,
};
}

return {
type: PatternType.Radial,
stops: [
{ color: startColor, position: 0 },
{ color: endColor, position: 1 },
],
};
}

function getColorRange(matches: RegExpMatchArray) {
const [
,
startColorR,
startColorG,
startColorB,
startAlpha,
startHex,
endColorR,
endColorG,
endColorB,
endAlpha,
endHex,
] = matches;

const startColor = startHex
? createSolidFromString(`#${startHex}`).color
: parseColor(startColorR, startColorG, startColorB, startAlpha);

const endColor = endHex
? createSolidFromString(`#${endHex}`).color
: parseColor(endColorR, endColorG, endColorB, endAlpha);

return { startColor, endColor };
}

function parseColor(r: string, g: string, b: string, a: string) {
return {
r: parseInt(r),
g: parseInt(g),
b: parseInt(b),
a: a ? parseFloat(a) : undefined,
};
}
26 changes: 26 additions & 0 deletions packages/patterns/src/getGradientStyleFromColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Internal dependencies
*/
import generatePatternStyles from './generatePatternStyles';
import type { Gradient } from './types';

export default function getGradientStyleFromColor(color: Gradient) {
const gradient = generatePatternStyles(color);

return gradient.backgroundImage;
}
2 changes: 2 additions & 0 deletions packages/patterns/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ export { default as hasGradient } from './hasGradient';
export { default as hasOpacity } from './hasOpacity';
export { default as isPatternEqual } from './isPatternEqual';
export { default as isHexColorString } from './isHexColorString';
export { default as getGradientStyleFromColor } from './getGradientStyleFromColor';
export { default as getColorFromGradientStyle } from './getColorFromGradientStyle';
1 change: 1 addition & 0 deletions packages/rich-text/src/customConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ export const WEIGHT = 'CUSTOM-WEIGHT';
export const COLOR = 'CUSTOM-COLOR';
export const LETTERSPACING = 'CUSTOM-LETTERSPACING';
export const UPPERCASE = 'CUSTOM-UPPERCASE';
export const GRADIENT_COLOR = 'CUSTOM-GRADIENT-COLOR';

export const MULTIPLE_VALUE = '((MULTIPLE))';
121 changes: 121 additions & 0 deletions packages/rich-text/src/formatters/gradientColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import {
createSolid,
generatePatternStyles,
getGradientStyleFromColor,
isPatternEqual,
getColorFromGradientStyle,
} from '@googleforcreators/patterns';
import type { Gradient, Pattern } from '@googleforcreators/patterns';
import type { EditorState, DraftInlineStyle } from 'draft-js';
import type { CSSProperties } from 'react';

/**
* Internal dependencies
*/
import { NONE, MULTIPLE_VALUE, GRADIENT_COLOR } from '../customConstants';
import {
togglePrefixStyle,
getPrefixStylesInSelection,
} from '../styleManipulation';
import { isStyle, getVariable } from './util';

const styleToColor = (style: string): Gradient =>
getColorFromGradientStyle(getVariable(style, GRADIENT_COLOR));

const colorToStyle = (color: Gradient): string =>
`${GRADIENT_COLOR}-${getGradientStyleFromColor(color)}`;

function elementToStyle(element: HTMLElement): string | null {
const isSpan = element.tagName.toLowerCase() === 'span';
const rawBackground = element.style.background;
const hasBackground = Boolean(rawBackground);

if (isSpan && hasBackground) {
const gradient = getColorFromGradientStyle(rawBackground);
return colorToStyle(gradient);
}

return null;
}

function stylesToCSS(styles: DraftInlineStyle): null | CSSProperties {
const colorStyle = styles.find((someStyle) =>
isStyle(someStyle, GRADIENT_COLOR)
);

if (!colorStyle) {
return null;
}

let color: Pattern;
try {
color = styleToColor(colorStyle);
} catch (e) {
return null;
}

const css = generatePatternStyles(color);

return {
background: css.backgroundImage,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
};
}

function getColor(editorState: EditorState): Pattern | '((MULTIPLE))' {
const styles = getPrefixStylesInSelection(editorState, GRADIENT_COLOR);
if (styles.length > 1) {
return MULTIPLE_VALUE;
}
const colorStyle = styles[0];
if (colorStyle === NONE) {
return createSolid(0, 0, 0);
}
return styleToColor(colorStyle);
}

function setColor(editorState: EditorState, color: Gradient) {
const isBlack = isPatternEqual(createSolid(0, 0, 0), color);
const shouldSetStyle = () => !isBlack;
const getStyleToSet = () => colorToStyle(color);
return togglePrefixStyle(
editorState,
GRADIENT_COLOR,
shouldSetStyle,
getStyleToSet
);
}

const formatter = {
elementToStyle,
stylesToCSS,
autoFocus: false,
getters: {
gradientColor: getColor,
},
setters: {
setGradientColor: setColor,
},
};

export default formatter;
2 changes: 2 additions & 0 deletions packages/rich-text/src/formatters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import italicFormatter from './italic';
import underlineFormatter from './underline';
import colorFormatter from './color';
import gradientColorFormatter from './gradientColor';
import letterSpacingFormatter from './letterSpacing';
import uppercaseFormatter from './uppercase';

Expand All @@ -32,6 +33,7 @@
colorFormatter,
letterSpacingFormatter,
uppercaseFormatter,
gradientColorFormatter

Check failure on line 36 in packages/rich-text/src/formatters/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rich-text/src/formatters/index.ts#L36

[prettier/prettier] Insert `,`
];

export default formatters;
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import { __ } from '@googleforcreators/i18n';
import { useCallback } from '@googleforcreators/react';
import { trackEvent } from '@googleforcreators/tracking';
import { hasGradient, createSolid } from '@googleforcreators/patterns';

/**
* Internal dependencies
Expand Down Expand Up @@ -51,22 +52,33 @@ function TextColor() {
},
[updateElementsById, selectedElementIds]
);

const onColorChange = (color) => {
if (hasGradient(color)) {
handleSetColor(createSolid(0, 0, 0));
handleSetGradientColor(color);
} else {
handleSetGradientColor(createSolid(0, 0, 0));
handleSetColor(color);
}
};

const {
textInfo: { color },
handlers: { handleSetColor },
textInfo: { color, gradientColor },
handlers: { handleSetColor, handleSetGradientColor },
} = useRichTextFormatting([{ content, type: 'text' }], pushUpdate);

return (
<Color
tabIndex={-1}
label={__('Text color', 'web-stories')}
value={color}
allowsSavedColors
onChange={handleSetColor}
value={hasGradient(gradientColor) ? gradientColor : color}
onChange={onColorChange}
hasInputs={false}
hasEyedropper
allowsOpacity
allowsGradient={false}
allowsGradient
/>
);
}
Expand Down
Loading
Loading