Skip to content

Commit

Permalink
linear gradient px support
Browse files Browse the repository at this point in the history
  • Loading branch information
intergalacticspacehighway committed Jan 4, 2025
1 parent 6913cdb commit f43f4c4
Show file tree
Hide file tree
Showing 6 changed files with 526 additions and 277 deletions.
228 changes: 121 additions & 107 deletions packages/react-native/Libraries/StyleSheet/processBackgroundImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ type LinearGradientDirection =
| {type: 'angle', value: number}
| {type: 'keyword', value: string};

// null color stops indicate that the transition hint syntax is used. e.g. red, 20%, blue
// null color indicate that the transition hint syntax is used. e.g. red, 20%, blue
type ColorStopColor = ProcessedColorValue | null;
type ColorStopPosition = number | string | null;

type ParsedGradientValue = {
type: 'linearGradient',
direction: LinearGradientDirection,
colorStops: $ReadOnlyArray<{
color: ColorStopColor,
position: number,
position: ColorStopPosition,
}>,
};

Expand All @@ -53,7 +54,7 @@ export default function processBackgroundImage(
for (const bgImage of backgroundImage) {
const processedColorStops: Array<{
color: ColorStopColor,
position: number | null,
position: ColorStopPosition,
}> = [];
for (let index = 0; index < bgImage.colorStops.length; index++) {
const colorStop = bgImage.colorStops[index];
Expand All @@ -65,10 +66,13 @@ export default function processBackgroundImage(
positions.length === 1
) {
const position = positions[0];
if (typeof position === 'string' && position.endsWith('%')) {
if (
typeof position === 'number' ||
(typeof position === 'string' && position.endsWith('%'))
) {
processedColorStops.push({
color: null,
position: parseFloat(position) / 100,
position,
});
} else {
// If a position is invalid, return an empty array and do not apply gradient. Same as web.
Expand All @@ -82,10 +86,13 @@ export default function processBackgroundImage(
}
if (positions != null && positions.length > 0) {
for (const position of positions) {
if (position.endsWith('%')) {
if (
typeof position === 'number' ||
(typeof position === 'string' && position.endsWith('%'))
) {
processedColorStops.push({
color: processedColor,
position: parseFloat(position) / 100,
position,
});
} else {
// If a position is invalid, return an empty array and do not apply gradient. Same as web.
Expand Down Expand Up @@ -131,12 +138,10 @@ export default function processBackgroundImage(
}
}

const fixedColorStops = getFixedColorStops(processedColorStops);

result = result.concat({
type: 'linearGradient',
direction,
colorStops: fixedColorStops,
colorStops: processedColorStops,
});
}
}
Expand Down Expand Up @@ -198,61 +203,62 @@ function parseCSSLinearGradient(
// Case 1: [color, position, position]
if (colorStopParts.length === 3) {
const color = colorStopParts[0];
const position1 = colorStopParts[1];
const position2 = colorStopParts[2];
const position1 = getPositionFromCSSValue(colorStopParts[1]);
const position2 = getPositionFromCSSValue(colorStopParts[2]);
const processedColor = processColor(color);
if (processedColor == null) {
// If a color is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
if (position1.endsWith('%') && position2.endsWith('%')) {
colorStops.push({
color: processedColor,
position: parseFloat(position1) / 100,
});
colorStops.push({
color: processedColor,
position: parseFloat(position2) / 100,
});
} else {

if (position1 == null || position2 == null) {
// If a position is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}

colorStops.push({
color: processedColor,
position: position1,
});
colorStops.push({
color: processedColor,
position: position2,
});
}
// Case 2: [color, position]
else if (colorStopParts.length === 2) {
const color = colorStopParts[0];
const position = colorStopParts[1];
const position = getPositionFromCSSValue(colorStopParts[1]);
const processedColor = processColor(color);
if (processedColor == null) {
// If a color is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
if (position.endsWith('%')) {
colorStops.push({
color: processedColor,
position: parseFloat(position) / 100,
});
} else {
if (position == null) {
// If a position is invalid, return an empty array and do not apply any gradient. Same as web.
return [];
}
colorStops.push({
color: processedColor,
position,
});
}
// Case 3: [color]
// Case 4: [position] => transition hint syntax
else if (colorStopParts.length === 1) {
if (colorStopParts[0].endsWith('%')) {
const position = getPositionFromCSSValue(colorStopParts[0]);
if (position != null) {
if (
lastStop != null &&
lastStop.length === 1 &&
lastStop[0].endsWith('%')
getPositionFromCSSValue(lastStop[0]) != null
) {
// If the last stop is a transition hint syntax, return an empty array and do not apply any gradient. Same as web.
return [];
}
colorStops.push({
color: null,
position: parseFloat(colorStopParts[0]) / 100,
position,
});
} else {
const processedColor = processColor(colorStopParts[0]);
Expand All @@ -272,12 +278,10 @@ function parseCSSLinearGradient(
lastStop = colorStopParts;
}

const fixedColorStops = getFixedColorStops(colorStops);

gradients.push({
type: 'linearGradient',
direction,
colorStops: fixedColorStops,
colorStops,
});
}

Expand Down Expand Up @@ -343,79 +347,89 @@ function getAngleInDegrees(angle?: string): ?number {
}
}

// https://drafts.csswg.org/css-images-4/#color-stop-fixup
function getFixedColorStops(
colorStops: $ReadOnlyArray<{
color: ColorStopColor,
position: number | null,
}>,
): Array<{
color: ColorStopColor,
position: number,
}> {
let fixedColorStops: Array<{
color: ColorStopColor,
position: number,
}> = [];
let hasNullPositions = false;
let maxPositionSoFar = colorStops[0].position ?? 0;
for (let i = 0; i < colorStops.length; i++) {
const colorStop = colorStops[i];
let newPosition = colorStop.position;
if (newPosition === null) {
// Step 1:
// If the first color stop does not have a position,
// set its position to 0%. If the last color stop does not have a position,
// set its position to 100%.
if (i === 0) {
newPosition = 0;
} else if (i === colorStops.length - 1) {
newPosition = 1;
}
}
// Step 2:
// If a color stop or transition hint has a position
// that is less than the specified position of any color stop or transition hint
// before it in the list, set its position to be equal to the
// largest specified position of any color stop or transition hint before it.
if (newPosition !== null) {
newPosition = Math.max(newPosition, maxPositionSoFar);
fixedColorStops[i] = {
color: colorStop.color,
position: newPosition,
};
maxPositionSoFar = newPosition;
} else {
hasNullPositions = true;
}
function getPositionFromCSSValue(position: string) {
if (position.endsWith('px')) {
return parseFloat(position);
}

// Step 3:
// If any color stop still does not have a position,
// then, for each run of adjacent color stops without positions,
// set their positions so that they are evenly spaced between the preceding and
// following color stops with positions.
if (hasNullPositions) {
let lastDefinedIndex = 0;
for (let i = 1; i < fixedColorStops.length; i++) {
if (fixedColorStops[i] !== undefined) {
const unpositionedStops = i - lastDefinedIndex - 1;
if (unpositionedStops > 0) {
const startPosition = fixedColorStops[lastDefinedIndex].position;
const endPosition = fixedColorStops[i].position;
const increment =
(endPosition - startPosition) / (unpositionedStops + 1);
for (let j = 1; j <= unpositionedStops; j++) {
fixedColorStops[lastDefinedIndex + j] = {
color: colorStops[lastDefinedIndex + j].color,
position: startPosition + increment * j,
};
}
}
lastDefinedIndex = i;
}
}
if (position.endsWith('%')) {
return position;
}

return fixedColorStops;
}

// https://drafts.csswg.org/css-images-4/#color-stop-fixup
// function getFixedColorStops(
// colorStops: $ReadOnlyArray<{
// color: ColorStopColor,
// position: ColorStopPosition,
// }>,
// ): Array<{
// color: ColorStopColor,
// position: ColorStopPosition,
// }> {
// let fixedColorStops: Array<{
// color: ColorStopColor,
// position: ColorStopPosition,
// }> = [];
// let hasNullPositions = false;
// let maxPositionSoFar = colorStops[0].position ?? 0;
// for (let i = 0; i < colorStops.length; i++) {
// const colorStop = colorStops[i];
// let newPosition = colorStop.position;
// if (newPosition === null) {
// // Step 1:
// // If the first color stop does not have a position,
// // set its position to 0%. If the last color stop does not have a position,
// // set its position to 100%.
// if (i === 0) {
// newPosition = 0;
// } else if (i === colorStops.length - 1) {
// newPosition = 1;
// }
// }
// Step 2:
// If a color stop or transition hint has a position
// that is less than the specified position of any color stop or transition hint
// before it in the list, set its position to be equal to the
// largest specified position of any color stop or transition hint before it.
// if (newPosition !== null) {
// newPosition = Math.max(newPosition, maxPositionSoFar);
// fixedColorStops[i] = {
// color: colorStop.color,
// position: newPosition,
// };
// maxPositionSoFar = newPosition;
// } else {
// hasNullPositions = true;
// }
// }

// Step 3:
// If any color stop still does not have a position,
// then, for each run of adjacent color stops without positions,
// set their positions so that they are evenly spaced between the preceding and
// following color stops with positions.
// if (hasNullPositions) {
// let lastDefinedIndex = 0;
// for (let i = 1; i < fixedColorStops.length; i++) {
// if (fixedColorStops[i] !== undefined) {
// const unpositionedStops = i - lastDefinedIndex - 1;
// if (unpositionedStops > 0) {
// const startPosition = fixedColorStops[lastDefinedIndex].position;
// const endPosition = fixedColorStops[i].position;
// const increment =
// (endPosition - startPosition) / (unpositionedStops + 1);
// for (let j = 1; j <= unpositionedStops; j++) {
// fixedColorStops[lastDefinedIndex + j] = {
// color: colorStops[lastDefinedIndex + j].color,
// position: startPosition + increment * j,
// };
// }
// }
// lastDefinedIndex = i;
// }
// }
// }

// return fixedColorStops;
// }
Loading

0 comments on commit f43f4c4

Please sign in to comment.