Skip to content

Commit

Permalink
improve support for nan and infinity (#1387)
Browse files Browse the repository at this point in the history
  • Loading branch information
romainmenke committed May 4, 2024
1 parent d31a632 commit f72d2c7
Show file tree
Hide file tree
Showing 27 changed files with 125 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/css-calc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Unreleased (patch)

- Allow uncapped precision in `calc()` serialization
- Add `censorIntoStandardRepresentableValues` conversion option

### 1.2.0

Expand Down
2 changes: 1 addition & 1 deletion packages/css-calc/dist/index.cjs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/css-calc/dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export declare type conversionOptions = {
* If you want to have outputs that are closes to CSS serialized values you can set `true`.
*/
toCanonicalUnits?: boolean;
/**
* Convert NaN, Infinity, ... into standard representable values.
*/
censorIntoStandardRepresentableValues?: boolean;
};

export declare type GlobalsWithStrings = Map<string, TokenDimension | TokenNumber | TokenPercentage | string>;
Expand Down
2 changes: 1 addition & 1 deletion packages/css-calc/dist/index.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/css-calc/docs/css-calc.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@
},
{
"kind": "Content",
"text": ";\n precision?: number;\n toCanonicalUnits?: boolean;\n}"
"text": ";\n precision?: number;\n toCanonicalUnits?: boolean;\n censorIntoStandardRepresentableValues?: boolean;\n}"
},
{
"kind": "Content",
Expand Down
1 change: 1 addition & 0 deletions packages/css-calc/docs/css-calc.conversionoptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type conversionOptions = {
globals?: GlobalsWithStrings;
precision?: number;
toCanonicalUnits?: boolean;
censorIntoStandardRepresentableValues?: boolean;
};
```
**References:** [GlobalsWithStrings](./css-calc.globalswithstrings.md)
Expand Down
5 changes: 5 additions & 0 deletions packages/css-calc/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ export type conversionOptions = {
* If you want to have outputs that are closes to CSS serialized values you can set `true`.
*/
toCanonicalUnits?: boolean,

/**
* Convert NaN, Infinity, ... into standard representable values.
*/
censorIntoStandardRepresentableValues?: boolean,
};
2 changes: 1 addition & 1 deletion packages/css-calc/src/util/infinity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function patchInfinity(x: TokenNode | FunctionNode | -1): TokenNode | Fun
return x;
}

if (Number.isFinite(token[4].value)) {
if (Number.isFinite(token[4].value) || Number.isNaN(token[4].value)) {
return x;
}

Expand Down
9 changes: 6 additions & 3 deletions packages/css-calc/src/util/patch-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import { patchCanonicalUnit } from './canonical-unit';
import { conversionOptions } from '../options';

export function patchCalcResult(x: TokenNode | FunctionNode | -1, options?: conversionOptions): TokenNode | FunctionNode | -1 {
let y: TokenNode | FunctionNode | -1;
y = patchNaN(x);
y = patchInfinity(y);
let y: TokenNode | FunctionNode | -1 = x;

if (!options?.censorIntoStandardRepresentableValues) {
y = patchNaN(y);
y = patchInfinity(y);
}

if (options?.toCanonicalUnits) {
y = patchCanonicalUnit(y);
Expand Down
6 changes: 5 additions & 1 deletion packages/css-calc/src/util/precision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { isFunctionNode } from '@csstools/css-parser-algorithms';
import { isTokenDimension, isTokenNumber, isTokenNumeric, isTokenPercentage } from '@csstools/css-tokenizer';

export function patchPrecision(x: TokenNode | FunctionNode | -1, precision = 13): TokenNode | FunctionNode | -1 {
if (x === -1 || precision < 0) {
if (x === -1) {
return -1;
}

if (precision <= 0) {
return x;
}

if (isFunctionNode(x)) {
return x;
}
Expand Down
20 changes: 20 additions & 0 deletions packages/css-calc/test/basic/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,23 @@ assert.strictEqual(
calc('sin(90deg)', { toCanonicalUnits: false }),
'1',
);

assert.strictEqual(
calc('calc(NaN)', { toCanonicalUnits: false }),
'calc(NaN)',
);

assert.strictEqual(
calc('calc(NaN)', { toCanonicalUnits: false, censorIntoStandardRepresentableValues: true }),
'NaN',
);

assert.strictEqual(
calc('calc(1 / 0)', { toCanonicalUnits: false }),
'calc(infinity)',
);

assert.strictEqual(
calc('calc(1 / 0)', { toCanonicalUnits: false, censorIntoStandardRepresentableValues: true }),
'Infinity',
);
4 changes: 4 additions & 0 deletions packages/css-color-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes to CSS Color Parser

### Unreleased (patch)

- Improve handling of `NaN` and `Infinity` in color functions.

### 2.0.0

_April 21, 2024_
Expand Down
2 changes: 1 addition & 1 deletion packages/css-color-parser/dist/index.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/css-color-parser/dist/index.mjs

Large diffs are not rendered by default.

22 changes: 12 additions & 10 deletions packages/css-color-parser/src/functions/color-mix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,20 @@ function colorMixComponents(componentValues: Array<ComponentValue>, colorParser:
isFunctionNode(node) &&
mathFunctionNames.has(toLowerCaseAZ(node.getName()))
) {
[[node]] = calcFromComponentValues([[node]], { toCanonicalUnits: true, precision: 100 });

if (
!node ||
!isTokenNode(node) ||
(
isTokenNumeric(node.value) &&
Number.isNaN(node.value[4].value)
)
) {
[[node]] = calcFromComponentValues([[node]], {
censorIntoStandardRepresentableValues: true,
precision: -1,
toCanonicalUnits: true,
});

if (!node || !isTokenNode(node) || !isTokenNumeric(node.value)) {
return false;
}

if (Number.isNaN(node.value[4].value)) {
// NaN does not escape a top-level calculation; it’s censored into a zero value
node.value[4].value = 0;
}
}

if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function normalize_Color_ChannelValues(token: CSSToken, index: number, co
colorData.syntaxFlags.add(SyntaxFlag.HasPercentageValues);
}

let value = normalize(token[4].value, 100, -Infinity, Infinity);
let value = normalize(token[4].value, 100, -2_147_483_647, 2_147_483_647);
if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
}
Expand All @@ -47,7 +47,7 @@ export function normalize_Color_ChannelValues(token: CSSToken, index: number, co
colorData.syntaxFlags.add(SyntaxFlag.HasNumberValues);
}

let value = normalize(token[4].value, 1, -Infinity, Infinity);
let value = normalize(token[4].value, 1, -2_147_483_647, 2_147_483_647);
if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
}
Expand Down
21 changes: 12 additions & 9 deletions packages/css-color-parser/src/functions/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,21 @@ export function color(colorFunctionNode: FunctionNode, colorParser: ColorParser)
return false;
}

const [[result]] = calcFromComponentValues([[node]], { toCanonicalUnits: true, precision: 100, globals: relativeColorChannelsWithoutNone });
if (
!result ||
!isTokenNode(result) ||
(
isTokenNumeric(result.value) &&
Number.isNaN(result.value[4].value)
)
) {
const [[result]] = calcFromComponentValues([[node]], {
censorIntoStandardRepresentableValues: true,
globals: relativeColorChannelsWithoutNone,
precision: -1,
toCanonicalUnits: true,
});
if (!result || !isTokenNode(result) || !isTokenNumeric(result.value)) {
return false;
}

if (Number.isNaN(result.value[4].value)) {
// NaN does not escape a top-level calculation; it’s censored into a zero value
result.value[4].value = 0;
}

node = result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export function normalize_modern_HSL_ChannelValues(token: CSSToken, index: numbe
if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
} else if (index === 1) {
value = normalize(token[4].value, 1, 0, Infinity);
value = normalize(token[4].value, 1, 0, 2_147_483_647);
}

return [
Expand All @@ -132,7 +132,7 @@ export function normalize_modern_HSL_ChannelValues(token: CSSToken, index: numbe
if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
} else if (index === 1) {
value = normalize(token[4].value, 1, 0, Infinity);
value = normalize(token[4].value, 1, 0, 2_147_483_647);
}

return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function normalize_Lab_ChannelValues(token: CSSToken, index: number, colo

let value = normalize(token[4].value, 1, 0, 100);
if (index === 1 || index === 2) {
value = normalize(token[4].value, 0.8, -Infinity, Infinity);
value = normalize(token[4].value, 0.8, -2_147_483_647, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
}
Expand All @@ -51,7 +51,7 @@ export function normalize_Lab_ChannelValues(token: CSSToken, index: number, colo

let value = normalize(token[4].value, 1, 0, 100);
if (index === 1 || index === 2) {
value = normalize(token[4].value, 1, -Infinity, Infinity);
value = normalize(token[4].value, 1, -2_147_483_647, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function normalize_LCH_ChannelValues(token: CSSToken, index: number, colo

let value = normalize(token[4].value, 1, 0, 100);
if (index === 1) {
value = normalize(token[4].value, (100 / 150), 0, Infinity);
value = normalize(token[4].value, (100 / 150), 0, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
}
Expand All @@ -65,7 +65,7 @@ export function normalize_LCH_ChannelValues(token: CSSToken, index: number, colo

let value = normalize(token[4].value, 1, 0, 100);
if (index === 1) {
value = normalize(token[4].value, 1, 0, Infinity);
value = normalize(token[4].value, 1, 0, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function normalize_OKLab_ChannelValues(token: CSSToken, index: number, co

let value = normalize(token[4].value, 100, 0, 1);
if (index === 1 || index === 2) {
value = normalize(token[4].value, 250, -Infinity, Infinity);
value = normalize(token[4].value, 250, -2_147_483_647, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
}
Expand All @@ -51,7 +51,7 @@ export function normalize_OKLab_ChannelValues(token: CSSToken, index: number, co

let value = normalize(token[4].value, 1, 0, 1);
if (index === 1 || index === 2) {
value = normalize(token[4].value, 1, -Infinity, Infinity);
value = normalize(token[4].value, 1, -2_147_483_647, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function normalize_OKLCH_ChannelValues(token: CSSToken, index: number, co

let value = normalize(token[4].value, 100, 0, 1);
if (index === 1) {
value = normalize(token[4].value, 250, 0, Infinity);
value = normalize(token[4].value, 250, 0, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
}
Expand All @@ -65,7 +65,7 @@ export function normalize_OKLCH_ChannelValues(token: CSSToken, index: number, co

let value = normalize(token[4].value, 1, 0, 1);
if (index === 1) {
value = normalize(token[4].value, 1, 0, Infinity);
value = normalize(token[4].value, 1, 0, 2_147_483_647);
} else if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function normalize_modern_sRGB_ChannelValues(token: CSSToken, index: numb
colorData.syntaxFlags.add(SyntaxFlag.HasPercentageValues);
}

let value = normalize(token[4].value, 100, -Infinity, Infinity);
let value = normalize(token[4].value, 100, -2_147_483_647, 2_147_483_647);
if (index === 3) {
value = normalize(token[4].value, 100, 0, 1);
}
Expand All @@ -93,7 +93,7 @@ export function normalize_modern_sRGB_ChannelValues(token: CSSToken, index: numb
colorData.syntaxFlags.add(SyntaxFlag.HasNumberValues);
}

let value = normalize(token[4].value, 255, -Infinity, Infinity);
let value = normalize(token[4].value, 255, -2_147_483_647, 2_147_483_647);
if (index === 3) {
value = normalize(token[4].value, 1, 0, 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,20 @@ export function threeChannelLegacySyntax(
return false;
}

const [[result]] = calcFromComponentValues([[node]], { toCanonicalUnits: true, precision: 100 });
if (
!result ||
!isTokenNode(result) ||
(
isTokenNumeric(result.value) &&
Number.isNaN(result.value[4].value)
)
) {
const [[result]] = calcFromComponentValues([[node]], {
censorIntoStandardRepresentableValues: true,
precision: -1,
toCanonicalUnits: true,
});
if (!result || !isTokenNode(result) || !isTokenNumeric(result.value)) {
return false;
}

if (Number.isNaN(result.value[4].value)) {
// NaN does not escape a top-level calculation; it’s censored into a zero value
result.value[4].value = 0;
}

node = result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,21 @@ export function threeChannelSpaceSeparated(
return false;
}

const [[result]] = calcFromComponentValues([[node]], { toCanonicalUnits: true, precision: 100, globals: relativeColorChannelsWithoutNone });
if (
!result ||
!isTokenNode(result) ||
(
isTokenNumeric(result.value) &&
Number.isNaN(result.value[4].value)
)
) {
const [[result]] = calcFromComponentValues([[node]], {
censorIntoStandardRepresentableValues: true,
globals: relativeColorChannelsWithoutNone,
precision: -1,
toCanonicalUnits: true,
});
if (!result || !isTokenNode(result) || !isTokenNumeric(result.value)) {
return false;
}

if (Number.isNaN(result.value[4].value)) {
// NaN does not escape a top-level calculation; it’s censored into a zero value
result.value[4].value = 0;
}

node = result;
}

Expand Down
9 changes: 7 additions & 2 deletions packages/css-color-parser/test/basic/basic.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ assert.deepStrictEqual(

assert.deepStrictEqual(
serialize_sRGB_data(color(parse('rgb(255 255 calc(0 / 0))'))),
'',
'rgb(255, 255, 0)',
);

assert.deepStrictEqual(
serialize_sRGB_data(color(parse('rgb(255 255 calc(1 / 0))'))),
'',
'rgb(255, 255, 255)',
);

assert.deepStrictEqual(
Expand Down Expand Up @@ -190,6 +190,11 @@ assert.deepStrictEqual(
'hsl(0, 0%, 100%)',
);

assert.deepStrictEqual(
serialize_HSL_data(color(parse('hsl(calc(1 / 0) 100% 50%)'))),
'hsl(0, 100%, 50%)',
);

assert.deepStrictEqual(
serialize_P3_data(color(parse('color(srgb 0.6 1 0.4)'))),
'color(display-p3 0.69403 0.98999 0.4832)',
Expand Down

0 comments on commit f72d2c7

Please sign in to comment.