diff --git a/.gitattributes b/.gitattributes index 9dbabfe0..8e82d9b5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -28,5 +28,9 @@ # do not replace lf by crlf *.css text *.json text +*.css text +*.js text +*.ts text +*.sh text text eol=lf diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 34d4bd29..f2d0597b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,8 +6,8 @@ jobs: runs-on: ubuntu-latest steps: # Your original steps - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 - name: Install run: npm install - name: Test and Coverage diff --git a/.github/workflows/jsr.yml b/.github/workflows/jsr.yml index b294277b..6fd7c3cf 100644 --- a/.github/workflows/jsr.yml +++ b/.github/workflows/jsr.yml @@ -14,5 +14,5 @@ jobs: contents: read id-token: write # The OIDC ID token is used for authentication with JSR. steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - run: npx jsr publish \ No newline at end of file diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 0fe18abe..b583ada5 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -17,13 +17,13 @@ jobs: strategy: fail-fast: false matrix: - node-version: [ 20.x, 22.x, 24.x ] + node-version: [ 22.x, 24.x, 26.x ] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - name: Install NPM dependencies diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 00000000..a829dd79 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,33 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node.js Windows CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + node-version: [ 22.x, 24.x, 26.x ] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v7 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - name: Install NPM dependencies + run: npm install + - name: Install playwright dependencies + run: npx playwright install --with-deps + - run: npm run 'test:node' diff --git a/.gitignore b/.gitignore index 9c1da5c1..a4f29e5e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,11 @@ test/*.html test/*.json test/*.md /docs +/deno.lock +/badges +/.nyc_output +/.repl_history +/CPU.* # Logs logs diff --git a/.npmignore b/.npmignore index 475530af..644183ba 100644 --- a/.npmignore +++ b/.npmignore @@ -31,4 +31,8 @@ /test/css-modules/demo.css /test/img/ /test/main.mjs -/test/modules/ \ No newline at end of file +/test/modules/ +/playground +/deno.lock +/scripts +/typedoc.config.js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cdd71b3..6a0d0cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,198 @@ # Changelog -## v1.4.3 +## v1.4.4 + +### Validation changes + +- [x] Added the `state` and `errors` properties to nodes to expose their validation status and any associated validation errors. +- [x] Nodes that fail validation are no longer discarded. Nodes are discarded only if they are invalid, unparsed, or malformed. Unknown nodes are discarded when lenient parsing is disabled. + +### Color + +- [x] `alpha()`: Added support for minifying the CSS Color 5 `alpha()` color function. + +```ts + +import {transform} from '@tbela99/css-parser'; + +const css = ` + +:root { +--mycolor: oklch(60% 0.25 315 / 0.8); +} + .s { + + color: + alpha(from var(--mycolor) / calc(alpha * 0.5)); + +`; + +const result = await transform(css, { + beautify: true, + inlineCssVariables: true, + }); + +console.debug(result.code); + +``` + +result: +```css +.s { + color: #b538e366 +} +``` + +- [x] `color-mix()`: Implemented support for the updated `color-mix()` syntax defined in https://www.w3.org/TR/css-values-5/#color-mix + + +```ts + +import {transform} from '@tbela99/css-parser'; + +const css = ` +.hsl { color: color-mix(firebrick, goldenrod); } +`; + +const result = await transform(css, { + beautify: true, + convertColor: ColorType.OKLAB + }); + +console.debug(result.code); + +``` + +result: + +```css +.hsl { + color: oklab(.624172 .087878 .113592) +} +``` + +### Webkit gradient prefix removal + +- [x] Convert -webkit-* gradient functions + - [x] -webkit-gradient() to: + - [x] linear-gradient() + - [ ] radial-gradient() - not supported ⚠️ + - [x] -webkit-linear-gradient() + - [x] -webkit-repeating-linear-gradient() + - [x] -webkit-radial-gradient() + - [x] -webkit-repeating-radial-gradient() + +### CSS gradient minification + +- [x] Minify gradient functions + - [x] linear-gradient() + - [x] radial-gradient() + - [x] conic-gradient() + - [x] repeating-linear-gradient() + - [x] repeating-radial-gradient() + - [x] repeating-conic-gradient() + +### Other changes + +- [x] Minify transform-origin property +- [x] Changing oklab distance threshold to `0.00001` according to https://drafts.csswg.org/css-color-4/#comparing-color-values + +### Bug fixes +- [x] Fix path resolution on Windows +- [x] Do not minify supports() arguments + +CSS incorrectly parsed: + +```css +body { + background-color: if( + supports(color: oklch(0.7 0.185 232)): oklch(0.7 0.185 232); + else: #00adf3; + ); + + &::after { + content: if( + supports(color: oklch(0.7 0.185 232)): "Your browser supports OKLCH"; + else: "Your browser does not support OKLCH"; + ); + } +} +``` + +result: + +```css +body { + background-color: if(supports(color:#00aefc):#00aefc;else:#00adf3); + &:after { + content: if(supports(color:#00aefc):"Your browser supports OKLCH";else:"Your browser does not support OKLCH") + } +} +``` + +instead of: + +```css +body { + background-color: #00adf3; + @supports (color:oklch(.7 .185 232)) { + background-color: #00aefc + } + &:after { + content: "Your browser does not support OKLCH"; + @supports (color:oklch(.7 .185 232)) { + content: "Your browser supports OKLCH" + } + } +} +``` + +- [x] Incorrectly parse selector when removePrefix and css module settings are enabled + +```css +*,:before,:after { + box-sizing: border-box +} +``` +is parsed and rendered as: + +```css +,,*:before:after { + box-sizing: border-box +} +``` + +instead of: + +```css +*,:before,:after { + box-sizing: border-box +} +``` + +### Deprecation + +#### Loading as commonjs module +- [x] Deprecate commonjs entry point. It will be removed in a future release. + +#### Functions +- [x] Deprecate `parseFile()`. Use `parse({file, asStream, ...options})` instead. +- [x] Deprecate `transformFile()`. Use `transform({file, asStream, ...options})` instead + +#### Enums +- [x] Deprecate using `ValidationLevel` to configure parsing validation settings. Use `boolean` values instead. +- [x] Deprecate invalid `EnumToken` node types. There are no longer used. +- [x] `EnumToken.InvalidRuleNodeType` +- [x] `EnumToken.InvalidAtRuleNodeType` +- [x] `EnumToken.InvalidDeclarationNodeType` + +#### Interfaces +- [x] `AstInvalidRule` +- [x] `AstInvalidDeclaration` +- [x] `AstInvalidAtRule` + +## v1.4.3 ### CSS Modules @@ -14,7 +205,6 @@ result = await transform(css, { beautify: true, module: {scoped: ModuleScopeEnumOptions.Shortest } - }); console.log(result.code); diff --git a/README.md b/README.md index 80817d8f..6e9655d0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![playground](https://img.shields.io/badge/playground-try%20it%20now-%230a7398 )](https://tbela99.github.io/css-parser/playground/) [![npm](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Ftbela99%2Fcss-parser%2Fmaster%2Fpackage.json&query=version&logo=npm&label=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) [![npm](https://img.shields.io/jsr/v/%40tbela99/css-parser?link=https%3A%2F%2Fjsr.io%2F%40tbela99%2Fcss-parser -)](https://jsr.io/@tbela99/css-parser) [![cov](https://tbela99.github.io/css-parser/badges/coverage.svg)](https://github.com/tbela99/css-parser/actions) [![Doc](https://img.shields.io/badge/online-documentation-blue)](https://tbela99.github.io/css-parser/docs) [![NPM Downloads](https://img.shields.io/npm/dm/%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) [![bundle size](https://img.shields.io/bundlejs/size/%40tbela99/css-parser%400.9.0?exports=cjs)](https://www.npmjs.com/package/@tbela99/css-parser) +)](https://jsr.io/@tbela99/css-parser) [![cov](https://tbela99.github.io/css-parser/badges/coverage.svg)](https://github.com/tbela99/css-parser/actions) [![Doc](https://tbela99.github.io/css-parser/docs/coverage.svg)](https://tbela99.github.io/css-parser/docs) [![NPM Downloads](https://img.shields.io/npm/dm/%40tbela99%2Fcss-parser)](https://www.npmjs.com/package/@tbela99/css-parser) [![bundle size](https://img.shields.io/bundlejs/size/%40tbela99/css-parser%400.9.0?exports=cjs)](https://www.npmjs.com/package/@tbela99/css-parser) # css-parser -CSS parser, minifier and validator for node and the browser +CSS parser, transformer, minifier and validator for node and the browser ## Installation @@ -22,852 +22,68 @@ $ deno add @tbela99/css-parser ## Features -- no dependency -- CSS validation based upon mdn-data -- CSS modules support -- fault-tolerant parser implementing the CSS syntax module 3 recommendations. -- fast and efficient minification without unsafe transforms, - see [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html) -- colors minification: color(), lab(), lch(), oklab(), oklch(), color-mix(), light-dark(), system colors and - relative color -- color conversion to any supported color format -- automatic nested css rules generation -- nested css rules conversion to legacy syntax -- convert css if() function to legacy syntax -- sourcemap generation -- css shorthands computation. see the supported properties list below -- css transform functions minification -- css math functions evaluation: calc(), clamp(), min(), max(), etc. -- css variables inlining -- duplicate properties removal -- @import rules flattening -- experimental CSS prefix removal - -## Online documentation - -See the full documentation at the [CSS Parser](https://tbela99.github.io/css-parser/docs) documentation site +* **Zero dependencies** — lightweight and easy to integrate into any project. +* **Standards-based CSS validation** powered by MDN data. +* **Full CSS Modules support** for modern component-based workflows. +* **Fault-tolerant parsing** that follows the CSS Syntax Module Level 3 specification. +* **High-performance minification** with safe optimizations and no unsafe transforms. +* **Advanced color processing** with support for modern color spaces and functions, including `color()`, `lab()`, `lch()`, `oklab()`, `oklch()`, `color-mix()`, `light-dark()`, system colors, and relative colors. +* **Color conversion engine** capable of transforming colors between all supported formats. +* **Automatic CSS nesting generation** from compatible selectors. +* **Source map generation** for easier debugging and development workflows. +* **Shorthand property computation** to reduce output size and improve optimization. +* **Transform function optimization** for more compact CSS output. +* **Math function evaluation**, including `calc()`, `clamp()`, `min()`, `max()`, and related expressions. +* **CSS variable inlining** where values can be safely resolved. +* **Duplicate declaration removal** to eliminate redundant rules. +* **`@import` flattening** to produce self-contained stylesheets. + +## Vendor prefix removal +**Experimental vendor prefix cleanup** to modernize generated CSS. + +## Syntax lowering +CSS-Parser can transform these modern CSS features into lower-level CSS syntax: +* **Nested CSS transpilation** to legacy-compatible syntax. +* **`if()` function transpilation** for broader browser compatibility. +* **`color-mix()` function conversion** convert new color-mix() syntax to any of the supported colors. + +## Benchmark + +Across all tested datasets, css-parser consistently produces the smallest output among the benchmarked tools, including Lightning CSS, cssnano, csso, clean-css, css-tree, and esbuild. + +The [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html) evaluates minification effectiveness on a diverse set of real-world stylesheets, including Bootstrap 4, Bootstrap 5, Tailwind CSS, Animate.css, Foundation, Font Awesome, Normalize.css, and others. + +A sample result: + +| File | ligthningcss | CSS Parser | +| ------------------------------ | ------------- | ------------- | +| tailwind.css - 2380419 bytes | 1864728 bytes | 1633251 bytes | +| bootstrap-4.css - 200078 bytes | 153616 bytes | 144700 bytes | +| bootstrap-5.css - 205481 bytes | 159987 bytes | 151258 bytes | + +On the [complete benchmark suite](https://tbela99.github.io/css-parser/benchmark/index.html), css-parser generated a total output size of 2,204,888 bytes, compared to 2,494,113 bytes for Lightning CSS and larger outputs for all other tested minifiers. +While some tools prioritize raw execution speed, css-parser focuses on maximizing compression while preserving stylesheet semantics, resulting in consistently smaller production bundles. ## Playground Try it [online](https://tbela99.github.io/css-parser/playground/) -## Import - -There are several ways to import the library into your application. - -### Node - -import as a module - -```javascript - -import {transform} from '@tbela99/css-parser'; - -// ... -``` - -### Deno - -import as a module - -```javascript - -import {transform} from '@tbela99/css-parser'; - -// ... -``` - -import as a CommonJS module - -```javascript - -const {transform} = require('@tbela99/css-parser/cjs'); - -// ... -``` - -### Web - -Programmatic import - -```javascript - -import {transform} from '@tbela99/css-parser/web'; - -// ... -``` - -Javascript module from cdn - -```html - - -``` - -Javascript module - -```javascript - - -``` - -Javascript umd module from cdn - -```html - - - -``` - -## Exported functions - -```ts - -/* parse and render css */ -transform(css: string | ReadableStream, options?: TransformOptions = {}): Promise -/* parse css */ -parse(css: string | ReadableStream, options?: ParseOptions = {}): Promise; -/* render ast */ -render(ast: AstNode, options?: RenderOptions = {}): RenderResult; -/* parse and render css file or url */ -transformFile(filePathOrUrl: string, options?: TransformOptions = {}, asStream?: boolean): Promise; -/* parse css file or url */ -parseFile(filePathOrUrl: string, options?: ParseOptions = {}, asStream?: boolean): Promise; - -``` - -## Transform - -Parse and render css in a single pass. - -### Example - -```javascript - -import {transform} from '@tbela99/css-parser'; - -const {ast, code, map, errors, stats} = await transform(css, {minify: true, resolveImport: true, cwd: 'files/css'}); -``` - -### Example - -Read from stdin with node using readable stream - -```typescript -import {transform} from "../src/node"; -import {Readable} from "node:stream"; -import type {TransformResult} from '../src/@types/index.d.ts'; - -const readableStream: ReadableStream = Readable.toWeb(process.stdin) as ReadableStream; -const result: TransformResult = await transform(readableStream, {beautify: true}); - -console.log(result.code); -``` - -### TransformOptions - -Include ParseOptions and RenderOptions - -#### ParseOptions - -> Minify Options - -- minify: boolean, optional. default to _true_. optimize ast. -- pass: number, optional. minification passes. default to 1 -- nestingRules: boolean, optional. automatically generated nested rules. -- expandNestingRules: boolean, optional. convert nesting rules into separate rules. will automatically set nestingRules - to false. -- expandIfSyntax: experimental, convert css if() function into legacy syntax. -- removeDuplicateDeclarations: boolean, optional. remove duplicate declarations. -- computeTransform: boolean, optional. compute css transform functions. -- computeShorthand: boolean, optional. compute shorthand properties. -- computeCalcExpression: boolean, optional. evaluate calc() expression -- inlineCssVariables: boolean, optional. replace some css variables with their actual value. they must be declared once - in the :root {} or html {} rule. -- removeEmpty: boolean, optional. remove empty rule lists from the ast. - -> CSS modules Options - -- module: boolean | ModuleCaseTransformEnum | ModuleScopeEnumOptions | ModuleOptions, optional. css modules options. - -> CSS Prefix Removal Options - -- removePrefix: boolean, optional. remove CSS prefixes. - -> Validation Options - -- validation: ValidationLevel | boolean, optional. enable validation. permitted values are: - - ValidationLevel.None: no validation - - ValidationLevel.Default: validate selectors and at-rules (default) - - ValidationLevel.All. validate all nodes - - true: same as ValidationLevel.All. - - false: same as ValidationLevel.None -- lenient: boolean, optional. preserve invalid tokens. - -> Sourcemap Options - -- src: string, optional. original css file location to be used with sourcemap, also used to resolve url(). -- sourcemap: boolean, optional. preserve node location data. - -> Ast Traversal Options - -- visitor: VisitorNodeMap, optional. node visitor used to transform the ast. - -> Urls and \@import Options - -- resolveImport: boolean, optional. replace @import rule by the content of the referenced stylesheet. -- resolveUrls: boolean, optional. resolve css 'url()' according to the parameters 'src' and 'cwd' - -> Misc Options -- removeCharset: boolean, optional. remove @charset. -- cwd: string, optional. destination directory used to resolve url(). -- signal: AbortSignal, optional. abort parsing. - -#### RenderOptions - -> Minify Options - -- beautify: boolean, optional. default to _false_. beautify css output. -- minify: boolean, optional. default to _true_. minify css values. -- withParents: boolean, optional. render this node and its parents. -- removeEmpty: boolean, optional. remove empty rule lists from the ast. -- expandNestingRules: boolean, optional. expand nesting rules. -- preserveLicense: boolean, force preserving comments starting with '/\*!' when minify is enabled. -- removeComments: boolean, remove comments in generated css. -- convertColor: boolean | ColorType, convert colors to the specified color. default to ColorType.HEX. supported values are: - - true: same as ColorType.HEX - - false: no color conversion - - ColorType.HEX - - ColorType.RGB or ColorType.RGBA - - ColorType.HSL - - ColorType.HWB - - ColorType.CMYK or ColorType.DEVICE_CMYK - - ColorType.SRGB - - ColorType.SRGB_LINEAR - - ColorType.DISPLAY_P3 - - ColorType.PROPHOTO_RGB - - ColorType.A98_RGB - - ColorType.REC2020 - - ColorType.XYZ or ColorType.XYZ_D65 - - ColorType.XYZ_D50 - - ColorType.LAB - - ColorType.LCH - - ColorType.OKLAB - - ColorType.OKLCH - -> Sourcemap Options - -- sourcemap: boolean | 'inline', optional. generate sourcemap. - -> Misc Options - -- indent: string, optional. css indention string. uses space character by default. -- newLine: string, optional. new line character. -- output: string, optional. file where to store css. url() are resolved according to the specified value. no file is - created though. -- cwd: string, optional. destination directory used to resolve url(). - -## Parsing - -### Usage - -```javascript - -parse(css, parseOptions = {}) -``` - -### Example - -````javascript - -const {ast, errors, stats} = await parse(css); -```` - -## Rendering - -### Usage - -```javascript -render(ast, RenderOptions = {}); -``` - -### Examples - -Rendering ast - -```javascript -import {parse, render} from '@tbela99/css-parser'; - -const css = ` -@media screen and (min-width: 40em) { - .featurette-heading { - font-size: 50px; - } - .a { - color: red; - width: 3px; - } -} -`; - -const result = await parse(css, options); - -// print declaration without parents -console.error(render(result.ast.chi[0].chi[1].chi[1], {withParents: false})); -// -> width:3px - -// print declaration with parents -console.debug(render(result.ast.chi[0].chi[1].chi[1], {withParents: true})); -// -> @media screen and (min-width:40em){.a{width:3px}} - -``` - -### CSS Modules - -CSS modules features are fully supported. refer to the [CSS modules](https://tbela99.github.io/css-parser/docs/documents/Guide.CSS_modules.html) documentation for more information. - -```javascript -import {transform} from '@tbela99/css-parser'; - -const css = ` -.table { - border-collapse: collapse; - width: 100%; -} - -.table td, .table th { - border: 1px solid #ddd; - padding: 8px; -} - -.table tr:nth-child(even){background-color: #f2f2f2;} - -.table tr:hover {background-color: #ddd;} - -.table th { - padding-top: 12px; - padding-bottom: 12px; - text-align: left; - background-color: #4CAF50; - color: white; -} -`; - -const result = await transform(css, {module: true}); - -// css code -console.log(result.code); -// css mapping -console.log(result.mapping); -``` - -### Convert colors - -```javascript -import {transform, ColorType} from '@tbela99/css-parser'; - - -const css = ` -.hsl { color: #b3222280; } -`; -const result: TransformResult = await transform(css, { - beautify: true, - convertColor: ColorType.SRGB -}); - -console.log(result.css); - -``` - -result - -```css -.hsl { - color: color(srgb .7019607843137254 .13333333333333333 .13333333333333333/50%) -} -``` - -### Merge similar rules - -CSS - -```css - -.clear { - width: 0; - height: 0; - color: transparent; -} - -.clearfix:before { - - height: 0; - width: 0; -} -``` - -```javascript - -import {transform} from '@tbela99/css-parser'; - -const result = await transform(css); - -``` - -Result - -```css -.clear, .clearfix:before { - height: 0; - width: 0 -} - -.clear { - color: #0000 -} -``` - -### Automatic CSS Nesting - -CSS - -```javascript -const {parse, render} = require("@tbela99/css-parser/cjs"); - -const css = ` -table.colortable td { - text-align:center; -} -table.colortable td.c { - text-transform:uppercase; -} -table.colortable td:first-child, table.colortable td:first-child+td { - border:1px solid black; -} -table.colortable th { - text-align:center; - background:black; - color:white; -} -`; - -const result = await parse(css, {nestingRules: true}).then(result => render(result.ast, {minify: false}).code); -``` - -Result - -```css -table.colortable { - & td { - text-align: center; - - &.c { - text-transform: uppercase - } - - &:first-child, &:first-child + td { - border: 1px solid #000 - } - } - - & th { - text-align: center; - background: #000; - color: #fff - } -} -``` - -### CSS Validation - -CSS - -```css - -#404 { - --animate-duration: 1s; -} - -.s, #404 { - --animate-duration: 1s; -} - -.s [type="text" { - --animate-duration: 1s; -} - -.s [type="text"]] -{ - --animate-duration: 1s; -} - -.s [type="text"] { - --animate-duration: 1s; -} - -.s [type="text" i] { - --animate-duration: 1s; -} - -.s [type="text" s] -{ - --animate-duration: 1s -; -} - -.s [type="text" b] -{ - --animate-duration: 1s; -} - -.s [type="text" b],{ - --animate-duration: 1s -; -} - -.s [type="text" b] -+ { - --animate-duration: 1s; -} - -.s [type="text" b] -+ b { - --animate-duration: 1s; -} - -.s [type="text" i] + b { - --animate-duration: 1s; -} - - -.s [type="text"())]{ - --animate-duration: 1s; -} -.s(){ - --animate-duration: 1s; -} -.s:focus { - --animate-duration: 1s; -} -``` - -with validation enabled - -```javascript -import {parse, render} from '@tbela99/css-parser'; - -const options = {minify: true, validate: true}; -const {code} = await parse(css, options).then(result => render(result.ast, {minify: false})); -// -console.debug(code); -``` - -```css -.s:is([type=text],[type=text i],[type=text s],[type=text i]+b,:focus) { - --animate-duration: 1s -} -``` - -with validation disabled - -```javascript -import {parse, render} from '@tbela99/css-parser'; - -const options = {minify: true, validate: false}; -const {code} = await parse(css, options).then(result => render(result.ast, {minify: false})); -// -console.debug(code); -``` - -```css -.s:is([type=text],[type=text i],[type=text s],[type=text b],[type=text b]+b,[type=text i]+b,:focus) { - --animate-duration: 1s -} -``` - -### Nested CSS Expansion - -CSS - -```css -table.colortable { - & td { - text-align: center; - - &.c { - text-transform: uppercase - } - - &:first-child, &:first-child + td { - border: 1px solid #000 - } - } - - & th { - text-align: center; - background: #000; - color: #fff - } -} -``` - -Javascript - -```javascript -import {parse, render} from '@tbela99/css-parser'; - -const options = {minify: true}; -const {code} = await parse(css, options).then(result => render(result.ast, {minify: false, expandNestingRules: true})); -// -console.debug(code); -``` - -Result - -```css - -table.colortable td { - text-align: center; -} - -table.colortable td.c { - text-transform: uppercase; -} - -table.colortable td:first-child, table.colortable td:first-child + td { - border: 1px solid black; -} - -table.colortable th { - text-align: center; - background: black; - color: white; -} -``` - -### CSS if() function expansion - -```typescript - -const css = ` -button { - background: linear-gradient( - if(media(min-width: 768px): to right; else: to bottom), - if(style(--dark-mode): #333; else: #fff), - if(style(--dark-mode): #000; else: #ccc) - ); -}`; - -result = await transform(css, { - - beautify: true, - expandIfSyntax: true - } - -}); - -console.log(result.code); -``` - -output - -```css -button { - background: linear-gradient(to bottom,#fff,#ccc); - @media (min-width:768px) { - background: linear-gradient(to right,#fff,#ccc); - @container style(--dark-mode) { - background: linear-gradient(to right,#333,#000) - } - } - @container style(--dark-mode) { - background: linear-gradient(to bottom,#333,#000) - } -} -``` - -### Calc() resolution - -```javascript - -import {parse, render} from '@tbela99/css-parser'; - -const css = ` -a { - -width: calc(100px * log(625, 5)); -} -.foo-bar { - width: calc(100px * 2); - height: calc(((75.37% - 63.5px) - 900px) + (2 * 100px)); - max-width: calc(3.5rem + calc(var(--bs-border-width) * 2)); -} -`; - -const prettyPrint = await parse(css).then(result => render(result.ast, {minify: false}).code); - -``` - -result - -```css -a { - width: 400px; -} - -.foo-bar { - width: 200px; - height: calc(75.37% - 763.5px); - max-width: calc(3.5rem + var(--bs-border-width) * 2) -} -``` - -### CSS variable inlining - -```javascript - -import {parse, render} from '@tbela99/css-parser'; - -const css = ` - -:root { - ---preferred-width: 20px; -} -.foo-bar { - - width: calc(calc(var(--preferred-width) + 1px) / 3 + 5px); - height: calc(100% / 4);} -` - -const prettyPrint = await parse(css, {inlineCssVariables: true}).then(result => render(result.ast, {minify: false}).code); - -``` - -result - -```css -.foo-bar { - width: 12px; - height: 25% -} - -``` - -### CSS variable inlining and relative color - -```javascript - -import {parse, render} from '@tbela99/css-parser'; - -const css = ` - -:root { ---color: green; -} -._19_u :focus { - color: hsl(from var(--color) calc(h * 2) s l); - -} -` - -const prettyPrint = await parse(css, {inlineCssVariables: true}).then(result => render(result.ast, {minify: false}).code); - -``` - -result - -```css -._19_u :focus { - color: navy -} - -``` - -### CSS variable inlining and relative color - -```javascript - -import {parse, render} from '@tbela99/css-parser'; - -const css = ` - -html { --bluegreen: oklab(54.3% -22.5% -5%); } -.overlay { - background: oklab(from var(--bluegreen) calc(1.0 - l) calc(a * 0.8) b); -} -` - -const prettyPrint = await parse(css, {inlineCssVariables: true}).then(result => render(result.ast, {minify: false}).code); - -``` - -result - -```css -.overlay { - background: #0c6464 -} - -``` - -# Node Walker - -```javascript - -import {walk} from '@tbela99/css-parser'; - -for (const {node, parent, root} of walk(ast)) { - - // do something -} -``` - +## Documentation + +- [Installation](https://tbela99.github.io/css-parser/docs/documents/Guide.Getting_started.html#installation) +- [Usage](https://tbela99.github.io/css-parser/docs/documents/Guide.Usage.html) + - [Parsing CSS](https://tbela99.github.io/css-parser/docs/documents/Guide.Usage.html#parsing) + - [Parsing files](https://tbela99.github.io/css-parser/docs/documents/Guide.Usage.html#parsing-files) + - [Parsing streams](https://tbela99.github.io/css-parser/docs/documents/Guide.Usage.html#parsing-from-a-readable-stream) + - [Rendering AST](https://tbela99.github.io/css-parser/docs/documents/Guide.Usage.html#rendering-ast) + - [Transform](https://tbela99.github.io/css-parser/docs/documents/Guide.Usage.html#transform) +- [Validation](https://tbela99.github.io/css-parser/docs/documents/Guide.Validation.html) +- [CSS Modules](https://tbela99.github.io/css-parser/docs/documents/Guide.CSS_Modules.html) +- [Minification](https://tbela99.github.io/css-parser/docs/documents/Guide.Minification.html) +- [Custom Transform](https://tbela99.github.io/css-parser/docs/documents/Guide.Custom_Transform.html) +- [Ast Manipulation](https://tbela99.github.io/css-parser/docs/documents/Guide.Ast_Manipulation.html) +- [Utility Functions](https://tbela99.github.io/css-parser/docs/documents/Guide.Utility_Functions.html) +- ## AST ### Comment @@ -880,18 +96,24 @@ for (const {node, parent, root} of walk(ast)) { - typ: number - nam: string, declaration name - val: array of tokens +- state: EnumAstNodeStatus, validation state +- errors: ErrorDescription[], validation errors ### Rule - typ: number - sel: string, css selector - chi: array of children +- state: EnumAstNodeStatus, validation state +- errors: ErrorDescription[], validation errors ### AtRule - typ: number - nam: string. AtRule name - val: rule prelude +- state: EnumAstNodeStatus, validation state +- errors: ErrorDescription[], validation errors ### AtRuleStyleSheet @@ -903,32 +125,12 @@ for (const {node, parent, root} of walk(ast)) { - typ: number - sel: string, css selector - chi: array of children +- state: EnumAstNodeStatus, validation state +- errors: ErrorDescription[], validation errors ## Sourcemap -- [x] sourcemap generation - -## Minification - -- [x] minify keyframes -- [x] minify transform functions -- [x] evaluate math functions calc(), clamp(), min(), max(), round(), mod(), rem(), sin(), cos(), tan(), asin(), - acos(), atan(), atan2(), pow(), sqrt(), hypot(), log(), exp(), abs(), sign() -- [x] minify colors -- [x] minify numbers and Dimensions tokens -- [x] multi-pass minification -- [x] inline css variables -- [x] merge identical rules -- [x] merge adjacent rules -- [x] compute shorthand: see the list below -- [x] remove redundant declarations -- [x] conditionally unwrap :is() -- [x] automatic css nesting -- [x] automatically wrap selectors using :is() -- [x] avoid reparsing (declarations, selectors, at-rule) -- [x] node and browser versions -- [x] decode and replace utf-8 escape sequence -- [x] experimental CSS prefix removal +- [x] Sourcemap generation ## Computed shorthands properties @@ -984,257 +186,4 @@ for (const {node, parent, root} of walk(ast)) { ## Performance -- [x] flatten @import - -## Node Transformation - -Ast can be transformed using node visitors - -### Example 1: Declaration - -the visitor is called for any declaration encountered - -```typescript - -import {AstDeclaration, ParserOptions} from "../src/@types"; - -const options: ParserOptions = { - - visitor: { - - Declaration: (node: AstDeclaration) => { - - if (node.nam == '-webkit-transform') { - - node.nam = 'transform' - } - } - } -} - -const css = ` - -.foo { - -webkit-transform: scale(calc(100 * 2/ 15)); -} -`; - -const result = await transform(css, options); -console.debug(result.code); - -// .foo{transform:scale(calc(40/3))} -``` - -### Example 2: Declaration - -the visitor is called only on 'height' declarations - -```typescript - -import {AstDeclaration, EnumToken, LengthToken, ParserOptions, transform} from '@tbela99/css-parser'; - -const options: ParserOptions = { - - visitor: { - - Declaration: { - - // called only for height declaration - height: (node: AstDeclaration): AstDeclaration[] => { - - - return [ - node, - { - - typ: EnumToken.DeclarationNodeType, - nam: 'width', - val: [ - { - typ: EnumToken.LengthTokenType, - val: 3, - unit: 'px' - } - ] - } - ]; - } - } - } -}; - -const css = ` - -.foo { - height: calc(100px * 2/ 15); -} -.selector { -color: lch(from peru calc(l * 0.8) calc(c * 0.7) calc(h + 180)) -} -`; - -const result = await transform(css, options); -console.debug(result.code); - -// .foo{height:calc(40px/3);width:3px}.selector{color:#0880b0} - -``` - -### Example 3: At-Rule - -the visitor is called on any at-rule - -```typescript - -import {AstAtRule, ParserOptions} from "../src/@types"; -import {transform} from "../src/node"; - - -const options: ParserOptions = { - - visitor: { - - AtRule: (node: AstAtRule): AstAtRule => { - - if (node.nam == 'media') { - - return {...node, val: 'all'} - } - } - } -}; - -const css = ` - -@media screen { - - .foo { - - height: calc(100px * 2/ 15); - } -} -`; - -const result = await transform(css, options); -console.debug(result.code); - -// .foo{height:calc(40px/3)} - -``` - -### Example 4: At-Rule - -the visitor is called only for at-rule media - -```typescript - -import {AstAtRule, ParserOptions} from "../src/@types"; -import {transform} from "../src/node"; - -const options: ParserOptions = { - - visitor: { - - AtRule: { - - media: (node: AstAtRule): AstAtRule => { - - node.val = 'all'; - return node - } - } - } -}; - -const css = ` - -@media screen { - - .foo { - - height: calc(100px * 2/ 15); - } -} -`; - -const result = await transform(css, options); -console.debug(result.code); - -// .foo{height:calc(40px/3)} - -``` - -### Example 5: Rule - -the visitor is called on any Rule - -```typescript - -import {AstAtRule, ParserOptions} from "../src/@types"; -import {transform} from "../src/node"; - -const options: ParserOptions = { - - visitor: { - - Rule(node: AstRule): AstRule { - - node.sel = '.foo,.bar,.fubar' - return node; - } - } -}; - -const css = ` - - .foo { - - height: calc(100px * 2/ 15); - } -`; - -const result = await transform(css, options); -console.debug(result.code); - -// .foo,.bar,.fubar{height:calc(40px/3)} - -``` - -### Example 6: Rule - -Adding declarations to any rule - -```typescript - -import {AstRule, parseDeclarations, ParserOptions, transform} from '@tbela99/css-parser'; - -const options: ParserOptions = { - - removeEmpty: false, - visitor: { - - Rule: async (node: AstRule): Promise => { - - if (node.sel == '.foo') { - - node.chi.push(...await parseDeclarations('width: 3px')); - return node; - } - - return null; - } - } -}; - -const css = ` - -.foo { -} -`; - -const result = await transform(css, options); -console.debug(result.code); - -// .foo{width:3px} - -``` +- [x] Bundle file referenced by the @import statement. diff --git a/dist/config.json.js b/dist/config.json.js index 1db687dc..12c7300d 100644 --- a/dist/config.json.js +++ b/dist/config.json.js @@ -1503,9 +1503,50 @@ var map = { shorthand: "background" } }; +var property = { + "transform-origin": { + pattern: [ + [ + "left|center|right", + "top|center|bottom", + "" + ], + [ + "left|center|right", + "top|center|bottom" + ], + [ + "center|left|right|center|top|bottom|" + ] + ], + mapping: { + center: { + typ: "Perc", + val: 50 + }, + left: { + typ: "Perc", + val: 0 + }, + right: { + typ: "Perc", + val: 100 + }, + top: { + typ: "Perc", + val: 0 + }, + bottom: { + typ: "Perc", + val: 100 + } + } + } +}; var config = { properties: properties, - map: map + map: map, + property: property }; -export { config as default, map, properties }; +export { config as default, map, properties, property }; diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index bc6dd52d..8e9501bb 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -5,7 +5,7 @@ })(this, (function (exports) { 'use strict'; /** - * syntax validation enum + * Syntax validation enum */ var SyntaxValidationResult; (function (SyntaxValidationResult) { @@ -17,7 +17,46 @@ SyntaxValidationResult[SyntaxValidationResult["Lenient"] = 2] = "Lenient"; })(SyntaxValidationResult || (SyntaxValidationResult = {})); /** - * enum of validation levels + * Enum of node statuses + */ + exports.EnumAstNodeStatus = void 0; + (function (EnumAstNodeStatus) { + /** + * Node passed validation + */ + EnumAstNodeStatus[EnumAstNodeStatus["Validated"] = 0] = "Validated"; + /** + * Node is invalid + */ + EnumAstNodeStatus[EnumAstNodeStatus["Invalid"] = 1] = "Invalid"; + /** + * node is not validated + */ + EnumAstNodeStatus[EnumAstNodeStatus["Unvalidated"] = 2] = "Unvalidated"; + /** + * Node did not pass validation, but is preserved. + */ + EnumAstNodeStatus[EnumAstNodeStatus["ValidationFailed"] = 3] = "ValidationFailed"; + /** + * Parsing the node is not supported yet or the node syntax definition does not exist. + */ + EnumAstNodeStatus[EnumAstNodeStatus["Unknown"] = 4] = "Unknown"; + /** + * Failed to parse the node. + */ + EnumAstNodeStatus[EnumAstNodeStatus["Unparsed"] = 5] = "Unparsed"; + /** + * Node is disallowed in the context + */ + EnumAstNodeStatus[EnumAstNodeStatus["Disallowed"] = 6] = "Disallowed"; + /** + * Node is malformed + */ + EnumAstNodeStatus[EnumAstNodeStatus["Malformed"] = 7] = "Malformed"; + })(exports.EnumAstNodeStatus || (exports.EnumAstNodeStatus = {})); + /** + * Enum of validation levels + * @deprecated */ exports.ValidationLevel = void 0; (function (ValidationLevel) { @@ -45,9 +84,13 @@ * validate selectors, at-rules and declarations */ ValidationLevel[ValidationLevel["All"] = 7] = "All"; + /** + * Report only. Apply validation and report nodes that are marked as invalid + */ + ValidationLevel[ValidationLevel["ReportOnly"] = 8] = "ReportOnly"; })(exports.ValidationLevel || (exports.ValidationLevel = {})); /** - * enum of all token types + * Enum of all token types */ exports.EnumToken = void 0; (function (EnumToken) { @@ -378,7 +421,8 @@ */ EnumToken[EnumToken["NestingSelectorTokenType"] = 80] = "NestingSelectorTokenType"; /** - * invalid rule token type + * Invalid rule token type + * @deprecated */ EnumToken[EnumToken["InvalidRuleNodeType"] = 81] = "InvalidRuleNodeType"; /** @@ -390,7 +434,8 @@ */ EnumToken[EnumToken["InvalidAttrTokenType"] = 83] = "InvalidAttrTokenType"; /** - * invalid at rule token type + * Invalid at rule token type + * @deprecated */ EnumToken[EnumToken["InvalidAtRuleNodeType"] = 84] = "InvalidAtRuleNodeType"; /** @@ -430,7 +475,8 @@ */ EnumToken[EnumToken["KeyframesAtRuleNodeType"] = 93] = "KeyframesAtRuleNodeType"; /** - * invalid declaration node type + * invalid declaration node type. + * @deprecated */ EnumToken[EnumToken["InvalidDeclarationNodeType"] = 94] = "InvalidDeclarationNodeType"; /* css module nodes */ @@ -727,7 +773,7 @@ EnumToken[EnumToken["TimelineFunction"] = 16] = "TimelineFunction"; })(exports.EnumToken || (exports.EnumToken = {})); /** - * supported color types enum + * Supported color types enum */ exports.ColorType = void 0; (function (ColorType) { @@ -831,6 +877,10 @@ * custom color */ ColorType[ColorType["CUSTOM_COLOR"] = 24] = "CUSTOM_COLOR"; + /** + * alpha() color function + */ + ColorType[ColorType["ALPHA"] = 25] = "ALPHA"; /** * alias for rgba */ @@ -848,6 +898,9 @@ */ ColorType[ColorType["DEVICE_CMYK"] = 7] = "DEVICE_CMYK"; })(exports.ColorType || (exports.ColorType = {})); + /** + * Supported module case transform + */ exports.ModuleCaseTransformEnum = void 0; (function (ModuleCaseTransformEnum) { /** @@ -871,6 +924,9 @@ */ ModuleCaseTransformEnum[ModuleCaseTransformEnum["DashCaseOnly"] = 16] = "DashCaseOnly"; })(exports.ModuleCaseTransformEnum || (exports.ModuleCaseTransformEnum = {})); + /** + * Supported module scope + */ exports.ModuleScopeEnumOptions = void 0; (function (ModuleScopeEnumOptions) { /** @@ -911,6 +967,11 @@ ModuleScopeEnumOptions[ModuleScopeEnumOptions["Shortest"] = 512] = "Shortest"; })(exports.ModuleScopeEnumOptions || (exports.ModuleScopeEnumOptions = {})); // https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Values_and_units#absolute_length_units + /** + * Convert length to px + * @param value + * @returns + */ function length2Px(value) { let result = null; if (value.typ == exports.EnumToken.NumberTokenType) { @@ -972,7 +1033,7 @@ } /** - * options for the walk function + * Options for the walk function */ exports.WalkerOptionEnum = void 0; (function (WalkerOptionEnum) { @@ -994,7 +1055,7 @@ WalkerOptionEnum[WalkerOptionEnum["IgnoreChildren"] = 8] = "IgnoreChildren"; })(exports.WalkerOptionEnum || (exports.WalkerOptionEnum = {})); /** - * event types for the walkValues function + * Event types for the walkValues function */ exports.WalkerEvent = void 0; (function (WalkerEvent) { @@ -1008,7 +1069,7 @@ WalkerEvent[WalkerEvent["Leave"] = 2] = "Leave"; })(exports.WalkerEvent || (exports.WalkerEvent = {})); /** - * walk ast nodes + * Walk ast nodes * @param node initial node * @param filter control the walk process * @param reverse walk in reverse order @@ -1125,7 +1186,7 @@ } } /** - * walk ast node value tokens + * Walk ast node value tokens * @param values * @param root * @param filter @@ -1217,7 +1278,6 @@ function* () { // @ts-expect-error let parent = map.get(node); - console.debug({ parent }); while (parent != null) { yield parent; parent = map.get(parent); @@ -1326,29 +1386,6 @@ } } - function dasherize(value) { - return value.replace(/([A-Z])/g, (all, one) => `-${one.toLowerCase()}`); - } - function camelize(value) { - return value.replace(/-([a-z])/g, (all, one) => one.toUpperCase()); - } - function equalsIgnoreCase(a, b) { - if (a.length !== b.length) - return false; - for (let i = 0; i < a.length; i++) { - let ca = a.charCodeAt(i); - let cb = b.charCodeAt(i); - // Normalize A-Z to a-z - if (ca >= 65 && ca <= 90) - ca += 32; - if (cb >= 65 && cb <= 90) - cb += 32; - if (ca !== cb) - return false; - } - return true; - } - var declarations = { "-ms-accelerator": { syntax: "false | true" @@ -2367,6 +2404,9 @@ "forced-color-adjust": { syntax: "auto | none | preserve-parent-color" }, + "frame-sizing": { + syntax: "auto | content-width | content-height | content-block-size | content-inline-size" + }, gap: { syntax: "<'row-gap'> <'column-gap'>?" }, @@ -3839,7 +3879,7 @@ syntax: "acos( )" }, "alpha-value": { - syntax: " | " + syntax: " | | none" }, "an+b": { syntax: "odd | even | | | '+'?† n | -n | | '+'?† | | | '+'?† n | -n | | '+'?† n- | -n- | ['+' | '-'] | '+'?† n ['+' | '-'] | -n ['+' | '-'] " @@ -4001,7 +4041,7 @@ syntax: "in [ | ? | ]" }, "color-mix()": { - syntax: "color-mix( , [ && ? ]#{2} )" + syntax: "color-mix( ,? [ && ? ]#)" }, "color-stop": { syntax: " | " @@ -4277,10 +4317,10 @@ syntax: "[ historical-ligatures | no-historical-ligatures ]" }, "hsl()": { - syntax: "hsl( , , , ? ) | hsl( [ | none ] [ | | none ] [ | | none ] [ / [ | none ] ]? )" + syntax: "hsl( , , , ? ) | hsl( [ | none ] [ | | none ] [ | | none ] [ / [ | none ] ]? ) | hsl(from [ | none | h | s | l ] {2} [ / [ | none] ]? )" }, "hsla()": { - syntax: "hsla( , , , ? ) | hsla( [ | none ] [ | | none ] [ | | none ] [ / [ | none ] ]? )" + syntax: "hsla( , , , ? ) | hsla( [ | none ] [ | | none ] [ | | none ] [ / [ | none ] ]? ) | hsla(from < [ | none | h | s | l ] {2} [ / [ | none ] ]? )" }, hue: { syntax: " | " @@ -4292,7 +4332,7 @@ syntax: "hue-rotate( [ | ]? )" }, "hwb()": { - syntax: "hwb( [ | none ] [ | | none ] [ | | none ] [ / [ | none ] ]? )" + syntax: "hwb( [ | none ] [ | | none ] [ | | none ] [ / [ | none ] ]? ) | hwb(from [ | none | h | w | b ] {2} [ / [ | none] ]? )" }, "hypot()": { syntax: "hypot( # )" @@ -4340,7 +4380,7 @@ syntax: " | " }, "lab()": { - syntax: "lab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? )" + syntax: "lab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? ) | lab(from {3} [ / [ | none ] ]? )" }, "layer()": { syntax: "layer( )" @@ -4349,7 +4389,7 @@ syntax: " [ '.' ]*" }, "lch()": { - syntax: "lch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? )" + syntax: "lch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? ) | lch(from {2} [ | none | l | c | h ] [ / [ | none ] ]? )" }, "leader()": { syntax: "leader( )" @@ -4514,10 +4554,10 @@ syntax: " | | " }, "oklab()": { - syntax: "oklab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? )" + syntax: "oklab( [ | | none] [ | | none] [ | | none] [ / [ | none] ]? ) | oklab(from {3} [ / [ | none ] ]? )" }, "oklch()": { - syntax: "oklch( [ from ]? [ | | none ] [ | | none ] [ | none ] [ / [ | none ] ]? )" + syntax: "oklch( [ | | none] [ | | none] [ | none] [ / [ | none] ]? ) | oklch(from {2} [ | none | l | c | h ] [ / [ | none ] ]? )" }, "opacity()": { syntax: "opacity( [ | ]? )" @@ -4667,10 +4707,10 @@ syntax: "reversed( )" }, "rgb()": { - syntax: "rgb( #{3} , ? ) | rgb( #{3} , ? ) | rgb( [ | | none ]{3} [ / [ | none ] ]? )" + syntax: "rgb( #{3} , ? ) | rgb( #{3} , ? ) | rgb( [ | | none ]{3} [ / [ | none ] ]? ) | rgb(from {3} [ / [ | none] ]? )" }, "rgba()": { - syntax: "rgba( #{3} , ? ) | rgba( #{3} , ? ) | rgba( [ | | none ]{3} [ / [ | none ] ]? )" + syntax: "rgba( #{3} , ? ) | rgba( #{3} , ? ) | rgba( [ | | none ]{3} [ / [ | none ] ]? ) | rgba(from {3} [ / [ | none ] ]? )" }, "rotate()": { syntax: "rotate( [ | ] )" @@ -4844,7 +4884,7 @@ syntax: "( )" }, "supports-feature": { - syntax: " | | | | | | " + syntax: " | " }, "supports-in-parens": { syntax: "( ) | | " @@ -4982,7 +5022,7 @@ syntax: "-moz-repeating-radial-gradient( <-legacy-radial-gradient-arguments> ) | -webkit-repeating-radial-gradient( <-legacy-radial-gradient-arguments> ) | -o-repeating-radial-gradient( <-legacy-radial-gradient-arguments> )" }, "-legacy-radial-gradient-arguments": { - syntax: "[ , ]? [ [ [ <-legacy-radial-gradient-shape> || <-legacy-radial-gradient-size> ] | [ | ]{2} ] , ]? " + syntax: " ? , [ [ <-legacy-radial-gradient-shape> || <-legacy-radial-gradient-size> ] | [ | ]{2} ]? , " }, "-legacy-radial-gradient-size": { syntax: "closest-side | closest-corner | farthest-side | farthest-corner | contain | cover" @@ -5185,6 +5225,21 @@ "syntax-string": { syntax: "" }, + "relative-hwb-component": { + syntax: " | | none | h | w | b | alpha" + }, + "relative-hsl-component": { + syntax: " | | none | h | s | l | alpha" + }, + "relative-rgb-component": { + syntax: " | | none | r | g | b | alpha" + }, + "relative-lab-component": { + syntax: " | | none | l | a | b | alpha" + }, + "relative-lch-component": { + syntax: " | | none | l | c | h | alpha" + }, "function-name": { syntax: "(" }, @@ -5806,7 +5861,7 @@ } }, "@font-feature-values": { - syntax: " @font-feature-values # { } ", + syntax: " @font-feature-values # { <@historical-forms> | <@styleset> | <@character-variant> | <@swash> | <@ornaments> | <@annotation> } ", descriptors: { "font-display": { syntax: "auto | block | swap | fallback | optional" @@ -5947,9 +6002,6 @@ } } }, - "@stylistic": { - syntax: " @stylistic { } " - }, "@historical-forms": { syntax: " @historical-forms { }" }, @@ -6669,6 +6721,8 @@ mediaFeatures: mediaFeatures }; + const regMatchLinearGradient = /^-((webkit)|o|moz)(-repeating)?-linear-gradient$/i; + const regMatchRadialGradient = /^-((webkit)|o|moz)(-repeating)?-radial-gradient$/i; const mFLT = new Set([exports.EnumToken.LtTokenType, exports.EnumToken.LteTokenType]); const mFGT = new Set([exports.EnumToken.GtTokenType, exports.EnumToken.GteTokenType]); const tokensMap = new Map([ @@ -6695,8 +6749,25 @@ [exports.EnumToken.GeneralEnclosedFunctionTokenDefType, exports.EnumToken.GeneralEnclosedFunctionTokenType], ]); const tokensfuncSet = new Set(tokensfuncDefMap.values()); + /** + * Epsilon + */ + const epsilon = 1e-5; + /** + * Color distance precision + */ + const colorDistancePrecision = epsilon; + /** + * Color precision + */ const colorPrecision = 6; + /** + * Angle precision + */ const anglePrecision = 0.001; + /** + * Color range definitions + */ const colorRange = { lab: { l: [0, 100], @@ -6816,18 +6887,27 @@ "lch", "light-dark", "contrast-color", + "alpha", ]; const imageFunc = [ "linear-gradient", "radial-gradient", "repeating-linear-gradient", "repeating-radial-gradient", + "repeating-conic-gradient", "conic-gradient", "image", "image-set", "element", "cross-fade", "paint", + "-o-linear-gradient", + "-moz-linear-gradient", + "-webkit-linear-gradient", + "-webkit-repeating-linear-gradient", + "-webkit-gradient", + "-webkit-radial-gradient", + "-webkit-repeating-radial-gradient", ]; const transformFunctions = [ "translate", @@ -7101,1437 +7181,951 @@ const definedPropertySettings = { configurable: true, enumerable: false, writable: true }; const combinators = ["+", ">", "~", "||", "|"]; - // https://www.w3.org/TR/CSS21/syndata.html#syntax - // https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/#typedef-ident-token - // '\\' - const REVERSE_SOLIDUS = 0x5c; - const dimensionUnits = new Set([ - "q", - "cap", - "ch", - "cm", - "cqb", - "cqh", - "cqi", - "cqmax", - "cqmin", - "cqw", - "dvb", - "dvh", - "dvi", - "dvmax", - "dvmin", - "dvw", - "em", - "ex", - "ic", - "in", - "lh", - "lvb", - "lvh", - "lvi", - "lvmax", - "lvw", - "mm", - "pc", - "pt", - "px", - "rem", - "rlh", - "svb", - "svh", - "svi", - "svmin", - "svw", - "vb", - "vh", - "vi", - "vmax", - "vmin", - "vw", - ]); - // https://www.w3.org/TR/css-values-4/#math-function - const pseudoElements = [":before", ":after", ":first-line", ":first-letter"]; - // https://developer.mozilla.org/en-US/docs/Web/CSS/WebKit_Extensions - // https://developer.mozilla.org/en-US/docs/Web/CSS/Mozilla_Extensions - const pseudoAliasMap = { - "-moz-center": "center", - "-webkit-center": "center", - "-ms-grid-columns": "grid-template-columns", - "-ms-grid-rows": "grid-template-rows", - "-ms-grid-row": "grid-row-start", - "-ms-grid-column": "grid-column-start", - "-ms-grid-row-align": "align-self", - "-ms-grid-row-span": "grid-row-end", - "-ms-grid-column-span": "grid-column-end", - "-ms-grid-column-align": "justify-self", - ":-ms-input-placeholder": "::placeholder", - "::-ms-input-placeholder": "::placeholder", - ":-moz-any()": ":is", - "-moz-user-modify": "user-modify", - "-webkit-match-parent": "match-parent", - "-moz-background-clip": "background-clip", - "-moz-background-origin": "background-origin", - "-ms-input-placeholder": "placeholder", - ":-webkit-autofill": ":autofill", - ":-webkit-any()": ":is", - "::-webkit-input-placeholder": "::placeholder", - "::-webkit-file-upload-button": "::file-selector-button", - "::-moz-placeholder": "::placeholder", - ":-webkit-any-link": ":any-link", - "-webkit-border-after": "border-block-end", - "-webkit-border-after-color": "border-block-end-color", - "-webkit-border-after-style": "border-block-end-style", - "-webkit-border-after-width": "border-block-end-width", - "-webkit-border-before": "border-block-start", - "-webkit-border-before-color": "border-block-start-color", - "-webkit-border-before-style": "border-block-start-style", - "-webkit-border-before-width": "border-block-start-width", - "-webkit-border-end": "border-inline-end", - "-webkit-border-end-color": "border-inline-end-color", - "-webkit-border-end-style": "border-inline-end-style", - "-webkit-border-end-width": "border-inline-end-width", - "-webkit-border-start": "border-inline-start", - "-webkit-border-start-color": "border-inline-start-color", - "-webkit-border-start-style": "border-inline-start-style", - "-webkit-border-start-width": "border-inline-start-width", - "-webkit-box-align": "align-items", - "-webkit-box-direction": "flex-direction", - "-webkit-box-flex": "flex-grow", - "-webkit-box-lines": "flex-flow", - "-webkit-box-ordinal-group": "order", - "-webkit-box-orient": "flex-direction", - "-webkit-box-pack": "justify-content", - "-webkit-column-break-after": "break-after", - "-webkit-column-break-before": "break-before", - "-webkit-column-break-inside": "break-inside", - "-webkit-font-feature-settings": "font-feature-settings", - "-webkit-hyphenate-character": "hyphenate-character", - "-webkit-initial-letter": "initial-letter", - "-webkit-margin-end": "margin-block-end", - "-webkit-margin-start": "margin-block-start", - "-webkit-padding-after": "padding-block-end", - "-webkit-padding-before": "padding-block-start", - "-webkit-padding-end": "padding-inline-end", - "-webkit-padding-start": "padding-inline-start", - "-webkit-min-device-pixel-ratio": "min-resolution", - "-webkit-max-device-pixel-ratio": "max-resolution", - "-webkit-font-smoothing": "font-smooth", - "-webkit-line-clamp": "line-clamp", - ":-webkit-autofill-strong-password": ":autofill", - ":-webkit-full-page-media": ":fullscreen", - ":-webkit-full-screen": ":fullscreen", - ":-webkit-full-screen-ancestor": ":fullscreen", - ":-webkit-full-screen-document": ":fullscreen", - ":-webkit-full-screen-controls-hidden": ":fullscreen", - "-moz-background-inline-policy": "box-decoration-break", - "-moz-background-size": "background-size", - "-moz-border-end": "border-inline-end", - "-moz-border-end-color": "border-inline-end-color", - "-moz-border-end-style": "border-inline-end-style", - "-moz-border-end-width": "border-inline-end-width", - "-moz-border-image": "border-inline-end-width", - "-moz-border-start": "border-inline-start", - "-moz-border-start-color": "border-inline-start-color", - "-moz-border-start-style": "border-inline-start-style", - "-moz-border-start-width": "border-inline-start-width", - "-moz-column-count": "column-count", - "-moz-column-fill": "column-fill", - "-moz-column-gap": "column-gap", - "-moz-column-width": "column-width", - "-moz-column-rule": "column-rule", - "-moz-column-rule-width": "column-rule-width", - "-moz-column-rule-style": "column-rule-style", - "-moz-column-rule-color": "column-rule-color", - "-moz-margin-end": "margin-inline-end", - "-moz-margin-start": "margin-inline-start", - "-moz-opacity": "opacity", - "-moz-outline": "outline", - "-moz-outline-color": "outline-color", - "-moz-outline-offset": "outline-offset", - "-moz-outline-style": "outline-style", - "-moz-outline-width": "outline-width", - "-moz-padding-end": "padding-inline-end", - "-moz-padding-start": "padding-inline-start", - "-moz-tab-size": "tab-size", - "-moz-text-align-last": "text-align-last", - "-moz-text-decoration-color": "text-decoration-color", - "-moz-text-decoration-line": "text-decoration-line", - "-moz-text-decoration-style": "text-decoration-style", - "-moz-transition": "transition", - "-moz-transition-delay": "transition-delay", - "-moz-transition-duration": "transition-duration", - "-moz-transition-property": "transition-property", - "-moz-transition-timing-function": "transition-timing-function", - "-moz-user-select": "user-select", - "-moz-initial": "initial", - "-moz-linear-gradient()": "linear-gradient", - "-moz-radial-gradient()": "radial-gradient", - "-moz-element()": "element", - "-moz-crisp-edges": "crisp-edges", - "-moz-calc()": "calc", - "-moz-min-content": "min-content", - "-moz-fit-content": "fit-content", - "-moz-max-content": "max-content", - "-moz-available": "stretch", - ":-moz-any-link": ":any-link", - ":-moz-full-screen": ":fullscreen", - ":-moz-full-screen-ancestor": ":fullscreen", - ":-moz-placeholder": ":placeholder-shown", - ":-moz-read-only": ":read-only", - ":-moz-read-write": ":read-write", - ":-moz-submit-invalid": ":invalid", - ":-moz-ui-invalid": ":user-invalid", - ":-moz-ui-valid": ":user-valid", - "::-moz-selection": "::selection", - }; - // renamed standard properties - const renamedStandardProperties = new Map([["color-adjust", "print-color-adjust"]]); - function isLength(dimension) { - return "unit" in dimension && dimensionUnits.has(dimension.unit.toLowerCase()); - } - function isResolution(dimension) { - return "unit" in dimension && ["dpi", "dpcm", "dppx", "x"].includes(dimension.unit.toLowerCase()); + function dasherize(value) { + return value.replace(/([A-Z])/g, (all, one) => `-${one.toLowerCase()}`); } - function isAngle(dimension) { - return "unit" in dimension && ["rad", "turn", "deg", "grad"].includes(dimension.unit.toLowerCase()); + function camelize(value) { + return value.replace(/-([a-z])/g, (all, one) => one.toUpperCase()); } - function isTime(dimension) { - return "unit" in dimension && ["ms", "s"].includes(dimension.unit.toLowerCase()); + function equalsIgnoreCase(a, b) { + if (a.length !== b.length) + return false; + for (let i = 0; i < a.length; i++) { + let ca = a.charCodeAt(i); + let cb = b.charCodeAt(i); + // Normalize A-Z to a-z + if (ca >= 65 && ca <= 90) + ca += 32; + if (cb >= 65 && cb <= 90) + cb += 32; + if (ca !== cb) + return false; + } + return true; } - function isFrequency(dimension) { - return "unit" in dimension && ["hz", "khz"].includes(dimension.unit.toLowerCase()); + + function getColorComponents(token) { + if (token.typ === exports.EnumToken.IdenTokenType) { + if (isColor(token)) { + parseColor(token); + } + else { + return null; + } + } + if (token.kin == exports.ColorType.HEX || token.kin == exports.ColorType.LIT) { + if (equalsIgnoreCase('currentcolor', token.val)) { + return null; + } + const value = expandHexValue(token.kin == exports.ColorType.LIT ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + // @ts-ignore + return value + .slice(1) + .match(/([a-fA-F0-9]{2})/g) + .map((t, index) => { + return { typ: exports.EnumToken.Number, val: index < 3 ? parseInt(t, 16) : parseInt(t, 16) / 255 }; + }); + } + const result = []; + for (const child of token.chi) { + if ([ + exports.EnumToken.LiteralTokenType, + exports.EnumToken.CommentTokenType, + exports.EnumToken.CommaTokenType, + exports.EnumToken.WhitespaceTokenType, + ].includes(child.typ)) { + continue; + } + if (child.typ === exports.EnumToken.IdenTokenType && isColor(child)) { + parseColor(child); + } + if (child.typ === exports.EnumToken.FunctionTokenType || + child.typ === exports.EnumToken.WildCardFunctionTokenType || + child.typ === exports.EnumToken.MathFunctionTokenType) { + if ("var" == child.val.toLowerCase()) { + return null; + } + else { + for (const { value } of walkValues(child.chi)) { + if (value.typ == exports.EnumToken.WildCardFunctionTokenDefType && + "var" === value.val.toLowerCase()) { + return null; + } + } + } + } + if (child.typ == exports.EnumToken.ColorTokenType && equalsIgnoreCase("currentcolor", child.val)) { + return null; + } + result.push(child); + } + return result; } - function isColorspace(token) { - if (token.typ === exports.EnumToken.WildCardFunctionTokenType && token.val === "var") { - return true; + + // from https://www.w3.org/TR/css-color-4/multiply-matrices.js + /** + * Simple matrix (and vector) multiplication + * Warning: No error handling for incompatible dimensions! + * @author Lea Verou 2020 MIT License + */ + // A is m x n. B is n x p. product is m x p. + function multiplyMatrices(A, B) { + let m = A.length; + if (!Array.isArray(A[0])) { + // A is vector, convert to [[a, b, c, ...]] + A = [A]; } - if (token.typ != exports.EnumToken.IdenTokenType) { - return false; + if (!Array.isArray(B[0])) { + // B is vector, convert to [[a], [b], [c], ...]] + B = B.map((x) => [x]); } - return [ - "srgb", - "srgb-linear", - "lab", - "oklab", - "lch", - "oklch", - "xyz", - "xyz-d50", - "xyz-d65", - "display-p3", - "a98-rgb", - "prophoto-rgb", - "rec2020", - "rgb", - "hsl", - "hwb", - ].includes(token.val.toLowerCase()); + let p = B[0].length; + let B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B + let product = A.map((row) => B_cols.map((col) => { + if (!Array.isArray(row)) { + return col.reduce((a, c) => a + c * row, 0); + } + return row.reduce((a, c, i) => a + c * (col[i] || 0), 0); + })); + if (m === 1) { + product = product[0]; // Avoid [[a, b, c, ...]] + } + if (p === 1) { + return product.map((x) => x[0]); // Avoid [[a], [b], [c], ...]] + } + return product; } - function isRectangularOrthogonalColorspace(token) { - if (token.typ != exports.EnumToken.IdenTokenType) { - return false; + + function hex2lchToken(token) { + const values = hex2lchvalues(token); + if (values == null) { + return null; } - return [ - "srgb", - "srgb-linear", - "display-p3", - "a98-rgb", - "prophoto-rgb", - "rec2020", - "lab", - "oklab", - "xyz", - "xyz-d50", - "xyz-d65", - ].includes(token.val.toLowerCase()); + return lchToken(values); } - function isPolarColorspace(token) { - if (token.typ != exports.EnumToken.IdenTokenType) { - return false; + function rgb2lchToken(token) { + const values = rgb2lchvalues(token); + if (values == null) { + return null; } - return ["hsl", "hwb", "lch", "oklch"].includes(token.val); + return lchToken(values); } - function isHueInterpolationMethod(token) { - if (!Array.isArray(token)) { - return token.typ == exports.EnumToken.IdenTokenType && "hue" === token.val?.toLowerCase?.(); + function hsl2lchToken(token) { + const values = hsl2lchvalues(token); + if (values == null) { + return null; } - if (token.length != 2 || token[0].typ != exports.EnumToken.IdenTokenType || token[1].typ != exports.EnumToken.IdenTokenType) { - return false; + return lchToken(values); + } + function hwb2lchToken(token) { + const values = hwb2lchvalues(token); + if (values == null) { + return null; } - return (["shorter", "longer", "increasing", "decreasing"].includes(token[0].val?.toLowerCase?.()) && - "hue" === token[1].val?.toLowerCase?.()); + return lchToken(values); } - function isIdentColor(token) { - return (token.typ == exports.EnumToken.ColorTokenType && - [exports.ColorType.SYS, exports.ColorType.DPSYS, exports.ColorType.LIT].includes(token.kin) && - isIdent(token.val)); + function cmyk2lchToken(token) { + const values = cmyk2lchvalues(token); + if (values == null) { + return null; + } + return lchToken(values); } - function isPercentageToken(token) { - return (token.typ == exports.EnumToken.PercentageTokenType || - (token.typ == exports.EnumToken.NumberTokenType && token.val == 0)); + function lab2lchToken(token) { + const values = lab2lchvalues(token); + if (values == null) { + return null; + } + return lchToken(values); } - function isColor(token, errors) { - if (token.typ == exports.EnumToken.WildCardFunctionTokenType) { - return true; + function oklab2lchToken(token) { + const values = oklab2lchvalues(token); + if (values == null) { + return null; } - if (token.typ == exports.EnumToken.FunctionTokenType) { - if (!colorsFunc.includes(token.val.toLowerCase())) { - return false; - } + return lchToken(values); + } + function oklch2lchToken(token) { + const values = oklch2lchvalues(token); + if (values == null) { + return null; } - if (token.typ == exports.EnumToken.ColorTokenType) { - if ("kin" in token && !("chi" in token)) { - return true; - } + return lchToken(values); + } + function color2lchToken(token) { + const values = color2lchvalues(token); + if (values == null) { + return null; } - if (token.typ == exports.EnumToken.IdenTokenType) { - const val = token.val.toLowerCase(); - if (systemColors.has(val) || deprecatedSystemColors.has(val) || nonStandardColors.has(val)) { - return true; - } - // named color - return val in COLORS_NAMES || "currentcolor" === val || "transparent" === val; + return lchToken(values); + } + function lchToken(values) { + values[2] = toPrecisionAngle(values[2]); + const chi = [ + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) }, + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) }, + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionAngle(values[2]) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { + typ: exports.EnumToken.PercentageTokenType, + val: toPrecisionValue(values[3], 2) * 100, + }); } - let isLegacySyntax = false; - if (token.typ === exports.EnumToken.FunctionTokenType || token.typ === exports.EnumToken.ColorTokenType) { - if (!colorsFunc.includes(token.val.toLowerCase())) { - return false; - } - if (token.chi.length > 0) { - // @ts-ignore - if (token.val === "light-dark") { - // @ts-ignore - const children = token.chi.filter((t) => [ - exports.EnumToken.IdenTokenType, - exports.EnumToken.NumberTokenType, - exports.EnumToken.LiteralTokenType, - exports.EnumToken.ColorTokenType, - exports.EnumToken.FunctionTokenType, - exports.EnumToken.PercentageTokenType, - exports.EnumToken.WildCardFunctionTokenType, - ].includes(t.typ)); - if (children.length != 2) { - errors?.push({ - message: "light-dark function must have 2 arguments", - node: token, - action: "drop", - }); - return false; - } - if (isColor(children[0]) && isColor(children[1])) { - return true; - } - } - // adding numbers and percentages is disallowed - // https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/lch#defining_relative_color_output_channel_components:~:text=Adding%20a%20%3Cpercentage%3E%20to%20a%20%3Cnumber%3E%2C%20for%20example%2C%20doesn%27t%20work - // @ts-ignore - for (const { value, parent } of walkValues(token.chi, token, (value) => value.typ === exports.EnumToken.WildCardFunctionTokenType - ? exports.WalkerOptionEnum.Ignore | exports.WalkerOptionEnum.IgnoreChildren - : null)) { - let k = 0; - let l; - let tk = null; - let tl = null; - if (value.typ === exports.EnumToken.BinaryExpressionTokenType) { - tk = value.l; - tl = value.r; - } - else if (parent?.typ === exports.EnumToken.MathFunctionTokenType && - parent.val === "calc") { - l = k + 1; - while (l + 1 < parent.chi.length) { - const tk = parent.chi[l]; - if (tk.typ === exports.EnumToken.WhitespaceTokenType || - tk.typ === exports.EnumToken.CommentTokenType || - tk.typ === exports.EnumToken.Add || - tk.typ === exports.EnumToken.Sub || - tk.typ === exports.EnumToken.Div || - tk.typ === exports.EnumToken.Mul) { - l++; - continue; - } - break; - } - tk = parent.chi[k]; - tl = parent.chi[l]; - } - if (tk != null && tl != null) { - if ((tk.typ === exports.EnumToken.PercentageTokenType || tl.typ === exports.EnumToken.PercentageTokenType) && - tk.typ !== tl.typ) { - errors?.push({ - action: "drop", - message: "adding percentage and number is not allowed", - node: token, - location: token.loc, - }); - return false; - } - } - } - // @ts-ignore - if (token.val == "color") { - // @ts-ignore - const children = token.chi.filter((t) => [ - exports.EnumToken.DashedIdenTokenType, - exports.EnumToken.IdenTokenType, - exports.EnumToken.NumberTokenType, - exports.EnumToken.LiteralTokenType, - exports.EnumToken.ColorTokenType, - exports.EnumToken.FunctionTokenType, - exports.EnumToken.MathFunctionTokenType, - exports.EnumToken.PercentageTokenType, - ].includes(t.typ)); - const isRelative = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == "from"; - let offset = 0; - if (isRelative) { - offset = 2; - } - if (children[offset]?.typ == exports.EnumToken.DashedIdenTokenType) { - if (children.length < offset + 1) { - return false; - } - for (let i = offset + 1; i < children.length; i++) { - if (children[i].typ == exports.EnumToken.NumberTokenType || - children[i].typ == exports.EnumToken.LiteralTokenType || - children[i].typ == exports.EnumToken.ColorTokenType || - children[i].typ == exports.EnumToken.FunctionTokenType || - children[i].typ == exports.EnumToken.MathFunctionTokenType || - children[i].typ == exports.EnumToken.PercentageTokenType || - isColor(children[i]) || - (children[i].typ == exports.EnumToken.IdenTokenType && - equalsIgnoreCase("none", children[i].val))) { - continue; - } - return false; - } - return true; - } - if (children.length < 4 || children.length > 8) { - return false; - } - if (!isRelative && !isColorspace(children[0])) { - return false; - } - for (let i = 1; i < children.length - 2; i++) { - if (children[i].typ == exports.EnumToken.IdenTokenType) { - if (isColor(children[i])) { - continue; - } - if (children[i].val != "none" && - !((isRelative && - ["alpha", "r", "g", "b", "x", "y", "z"].includes(children[i].val)) || - isColorspace(children[i]))) { - return false; - } - } - if (children[i].typ === exports.EnumToken.WildCardFunctionTokenType) { - continue; - } - if (children[i].typ === exports.EnumToken.FunctionTokenType || - children[i].typ === exports.EnumToken.MathFunctionTokenType) { - if (!mathFuncs.includes(children[i].val)) { - return false; - } - } - } - if (children.length == 4 || (isRelative && children.length == 6)) { - return true; - } - if (children.length == 8 || children.length == 6) { - const sep = children.at(-2); - const alpha = children.at(-1); - // @ts-ignore - if (((children.length > 6 || !isRelative) && sep.typ != exports.EnumToken.LiteralTokenType) || - sep.val != "/") { - return false; - } - if (alpha.typ == exports.EnumToken.IdenTokenType && alpha.val != "none") { - return false; - } - else { - // @ts-ignore - if (alpha.typ == exports.EnumToken.PercentageTokenType) { - if (+alpha.val < 0 || +alpha.val > 100) { - return false; - } - } - else if (alpha.typ == exports.EnumToken.NumberTokenType) { - if (+alpha.val < 0 || +alpha.val > 1) { - return false; - } - } - } - } - return true; - } - // @ts-ignore - else if (token.val == "color-mix") { - // @ts-ignore - const children = token.chi.reduce((acc, t) => { - if (t.typ == exports.EnumToken.CommaTokenType) { - acc.push([]); - } - else { - if (![exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType].includes(t.typ)) { - acc[acc.length - 1].push(t); - } - } - return acc; - }, [[]]); - if (children.length == 3) { - if (children[0].length > 4 || - children[0][0].typ != exports.EnumToken.IdenTokenType || - "in" !== children[0][0].val?.toLowerCase?.() || - !isColorspace(children[0][1]) || - (children[0].length >= 3 && !isHueInterpolationMethod(children[0].slice(2))) || - children[1].length > 2 || - !isColor(children[1][0]) || - (children[1].length == 2 && !isPercentageToken(children[1][1])) || - children[2].length > 2 || - (children[2].length == 2 && !isPercentageToken(children[2][1])) || - !isColor(children[2][0])) { - return false; - } - if (children[1].length == 2) { - if (!(children[1][1].typ == exports.EnumToken.PercentageTokenType || - (children[1][1].typ == exports.EnumToken.NumberTokenType && - children[1][1].val == 0))) { - return false; - } - } - if (children[2].length == 2) { - if (!(children[2][1].typ == exports.EnumToken.PercentageTokenType || - (children[2][1].typ == exports.EnumToken.NumberTokenType && - children[2][1].val == 0))) { - return false; - } - } - return true; - } - return false; - } - else { - const keywords = ["from", "none"]; - // @ts-ignore - if (["rgb", "hsl", "hwb", "lab", "lch", "oklab", "oklch"].includes(token.val)) { - // @ts-ignore - keywords.push("alpha", ...token.val.slice(-3).split("")); - } - // @ts-ignore - for (const v of token.chi) { - if (v.typ == exports.EnumToken.CommaTokenType) { - isLegacySyntax = true; - } - if (v.typ == exports.EnumToken.IdenTokenType) { - if (isColor(v)) { - continue; - } - if (!(keywords.includes(v.val) || v.val.toLowerCase() in COLORS_NAMES)) { - return false; - } - if (keywords.includes(v.val)) { - if (isLegacySyntax) { - return false; - } - // @ts-ignore - if (v.val == "from" && ["rgba", "hsla"].includes(token.val)) { - return false; - } - } - continue; - } - if (v.typ === exports.EnumToken.MathFunctionTokenType || - v.typ === exports.EnumToken.WildCardFunctionTokenType || - colorsFunc.includes(v.val)) { - continue; - } - if (![ - exports.EnumToken.ColorTokenType, - exports.EnumToken.IdenTokenType, - exports.EnumToken.NumberTokenType, - exports.EnumToken.AngleTokenType, - exports.EnumToken.PercentageTokenType, - exports.EnumToken.CommaTokenType, - exports.EnumToken.WhitespaceTokenType, - exports.EnumToken.LiteralTokenType, - ].includes(v.typ)) { - return false; - } - } - } - return true; - } - } - return false; + return { + typ: exports.EnumToken.ColorTokenType, + val: "lch", + chi, + kin: exports.ColorType.LCH, + }; } - function parseColor(token) { - if (token.typ === exports.EnumToken.IdenTokenType) { - const val = token.val.toLowerCase(); - if (nonStandardColors.has(val)) { - Object.assign(token, { - typ: exports.EnumToken.ColorTokenType, - kin: exports.ColorType.NON_STD, - }); - return token; - } - if (systemColors.has(val)) { - Object.assign(token, { - typ: exports.EnumToken.ColorTokenType, - kin: exports.ColorType.SYS, - }); - return token; - } - if (deprecatedSystemColors.has(val)) { - Object.assign(token, { - typ: exports.EnumToken.ColorTokenType, - kin: exports.ColorType.DPSYS, - }); - return token; - } - if (val in COLORS_NAMES || val === "currentcolor") { - Object.assign(token, { - typ: exports.EnumToken.ColorTokenType, - val, - kin: exports.ColorType.LIT, - }); - return token; - } - } - if (token.typ === exports.EnumToken.ColorTokenType) { - if (!("kin" in token) && "val" in token) { - // @ts-expect-error - token.kin = exports.ColorType[token.val.replaceAll("-", "_").toUpperCase()]; - } - if ("chi" in token) { - const tk = token.chi?.find((t) => t.typ !== exports.EnumToken.WhitespaceTokenType && t.typ !== exports.EnumToken.CommentTokenType); - if (tk?.typ === exports.EnumToken.IdenTokenType && tk.val === "from") { - token.cal = "rel"; - } - else if (token.val == "color-mix" && tk.val == "in") { - token.cal = "mix"; - } - if (token.val == "color") { - let index = token.chi.indexOf(tk); - if (token.cal == "rel") { - for (let k = 0; k < token.chi.length; k++) { - if (exports.EnumToken.DashedIdenTokenType == token.chi[k].typ) { - index = k; - break; - } - } - } - if (exports.EnumToken.DashedIdenTokenType == token?.chi?.[index]?.typ) { - token.kin = exports.ColorType.CUSTOM_COLOR; - } - } - } - return token; - } + function hex2lchvalues(token) { + const values = hex2labvalues(token); // @ts-ignore - token.typ = exports.EnumToken.ColorTokenType; + return values == null ? null : labvalues2lchvalues(...values); + } + function rgb2lchvalues(token) { + const values = rgb2labvalues(token); // @ts-ignore - token.kin = exports.ColorType[token.val.replaceAll("-", "_").toUpperCase()]; - if (!("chi" in token)) { - const val = token.val.toLowerCase(); - if (val == "currentcolor" || val == "transparent" || val in COLORS_NAMES) { - token.kin = exports.ColorType.LIT; - } - else if (isHexColor(val)) { - token.kin = exports.ColorType.HEX; - } - const tk = token.chi?.find((t) => t.typ !== exports.EnumToken.WhitespaceTokenType && t.typ !== exports.EnumToken.CommentTokenType); - if (tk?.typ === exports.EnumToken.IdenTokenType && tk.val === "from") { - token.cal = "rel"; - } - else if (token.val == "color-mix" && tk.val == "in") { - token.cal = "mix"; - } - else if (token.val == "color") { - token.cal = "col"; - } - return token; - } + return values == null ? null : labvalues2lchvalues(...values); + } + function hsl2lchvalues(token) { + const values = hsl2labvalues(token); // @ts-ignore - if (token.chi[0].typ == exports.EnumToken.IdenTokenType) { - // @ts-ignore - if (token.chi[0].val == "from") { - // @ts-ignore - token.cal = "rel"; - } - // @ts-ignore - else if (token.val == "color-mix" && token.chi[0].val == "in") { - // @ts-ignore - token.cal = "mix"; - } - else { - // @ts-ignore - if (token.val == "color") { - // @ts-ignore - token.cal = "col"; - } - } - } - return token; + return values == null ? null : labvalues2lchvalues(...values); } - function isLetter(codepoint) { - // lowercase - return ((codepoint >= 0x61 && codepoint <= 0x7a) || - // uppercase - (codepoint >= 0x41 && codepoint <= 0x5a)); + function hwb2lchvalues(token) { + const values = hwb2labvalues(token); + // @ts-ignore + return values == null ? null : labvalues2lchvalues(...values); } - function isNonAscii(codepoint) { - return codepoint >= 0x80; + function lab2lchvalues(token) { + const values = getLABComponents(token); + // @ts-ignore + return values == null ? null : labvalues2lchvalues(...values); } - function isIdentStart(codepoint) { - // _ - return codepoint == 0x5f || isLetter(codepoint) || isNonAscii(codepoint) || codepoint == REVERSE_SOLIDUS; + function srgb2lch(r, g, blue, alpha) { + // @ts-ignore + return labvalues2lchvalues(...srgb2labvalues(r, g, blue, alpha)); } - function isDigit(codepoint) { - return codepoint >= 0x30 && codepoint <= 0x39; + function oklab2lchvalues(token) { + const values = oklab2labvalues(token); + // @ts-ignore + return values == null ? null : labvalues2lchvalues(...values); } - function isIdentCodepoint(codepoint) { - // - - return codepoint == 0x2d || isDigit(codepoint) || isIdentStart(codepoint); + function cmyk2lchvalues(token) { + const values = cmyk2srgbvalues(token); + // @ts-ignore + return values == null ? null : srgb2lch(...values); } - function isIdent(name) { - const j = name.length - 1; - let i = 0; - let codepoint = name.charCodeAt(0); - // - - if (codepoint == 0x2d) { - const nextCodepoint = name.charCodeAt(1); - if (Number.isNaN(nextCodepoint)) { - return false; - } - if (nextCodepoint == REVERSE_SOLIDUS) { - return name.length > 2 && !isNewLine(name.charCodeAt(2)); - } - if (isDigit(nextCodepoint)) { - return false; - } - codepoint = nextCodepoint; - i = 1; + function oklch2lchvalues(token) { + const values = oklch2labvalues(token); + if (values == null) { + return null; } - if (codepoint !== 0x2d && !isIdentStart(codepoint)) { - return false; + // @ts-ignore + return labvalues2lchvalues(...values); + } + function color2lchvalues(token) { + const values = color2srgbvalues(token); + if (values == null) { + return null; } - if (codepoint == REVERSE_SOLIDUS) { - codepoint = name.charCodeAt(i + 1); - if (!isIdentCodepoint(codepoint)) { - return false; - } - i += String.fromCodePoint(codepoint).length; - if (i < j) { - codepoint = name.charCodeAt(i); - if (!isIdentCodepoint(codepoint)) { - return false; - } - } + // @ts-ignore + return srgb2lch(...values); + } + function labvalues2lchvalues(l, a, b, alpha = null) { + let c = Math.sqrt(a * a + b * b); + let h = (Math.atan2(b, a) * 180) / Math.PI; + if (h < 0) { + h += 360; } - while (i < j) { - i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; - codepoint = name.charCodeAt(i); - if (codepoint == REVERSE_SOLIDUS) { - i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; - codepoint = name.charCodeAt(i); - i += codepoint < 0x80 ? 1 : String.fromCodePoint(codepoint).length; - continue; - } - if (codepoint !== 0x2d && !isIdentCodepoint(codepoint)) { - return false; - } + if (c < 0.0001) { + c = h = 0; } - return true; - } - function isPseudo(name) { - return (name.charAt(0) == ":" && - ((name.endsWith("(") && isIdent(name.charAt(1) == ":" ? name.slice(2, -1) : name.slice(1, -1))) || - isIdent(name.charAt(1) == ":" ? name.slice(2) : name.slice(1)))); + return alpha == null ? [l, c, h] : [l, c, h, alpha]; } - function isHash(name) { - return name.charAt(0) == "#" && isIdent(name.charAt(1)); + function xyz2lchvalues(x, y, z, alpha) { + // @ts-ignore( + const lch = labvalues2lchvalues(...xyz2lab(x, y, z)); + return alpha == null || alpha == 1 ? lch : lch.concat(alpha); } - function isNumber(name) { - if (name.length == 0) { - return false; - } - let codepoint = name.charCodeAt(0); - let i = 0; - const j = name.length; - if (j == 1 && !isDigit(codepoint)) { - return false; + function getLCHComponents(token) { + const components = getColorComponents(token); + if (components == null) { + return null; } - // '+' '-' - if ([0x2b, 0x2d].includes(codepoint)) { - i++; + for (let i = 0; i < components.length; i++) { + if (![ + exports.EnumToken.NumberTokenType, + exports.EnumToken.PercentageTokenType, + exports.EnumToken.AngleTokenType, + exports.EnumToken.IdenTokenType, + ].includes(components[i].typ)) { + return null; + } } - // consume digits - while (i < j) { - codepoint = name.charCodeAt(i); - if (isDigit(codepoint)) { - i++; - continue; - } - // '.' 'E' 'e' - if (codepoint == 0x2e || codepoint == 0x45 || codepoint == 0x65) { - break; - } - return false; - } - // '.' - if (codepoint == 0x2e) { - if (!isDigit(name.charCodeAt(++i))) { - return false; - } - } - while (i < j) { - codepoint = name.charCodeAt(i); - if (isDigit(codepoint)) { - i++; - continue; - } - // 'E' 'e' - if (codepoint == 0x45 || codepoint == 0x65) { - i++; - break; - } - return false; - } - // 'E' 'e' - if (codepoint == 0x45 || codepoint == 0x65) { - if (i == j) { - return false; - } - codepoint = name.charCodeAt(i + 1); - // '+' '-' - if ([0x2b, 0x2d].includes(codepoint)) { - i++; - } - codepoint = name.charCodeAt(i + 1); - if (!isDigit(codepoint)) { - return false; - } - } - while (++i < j) { - codepoint = name.charCodeAt(i); - if (!isDigit(codepoint)) { - return false; - } - } - return true; + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const h = getAngle(t) * 360; + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + return alpha == null ? [l, c, h] : [l, c, h, alpha]; } - function isPercentage(name) { - return name.endsWith("%") && isNumber(name.slice(0, -1)); + + /* + */ + function xyzd502lch(x, y, z, alpha) { + // @ts-ignore + const [l, a, b] = xyz2lab(...XYZ_D50_to_D65(x, y, z)); + // L in range [0,100]. For use in CSS, add a percent + return labvalues2lchvalues(l, a, b, alpha); } - function isFlex(dimension) { - return "unit" in dimension && "fr" == dimension.unit.toLowerCase(); + function XYZ_D65_to_D50(x, y, z, alpha = null) { + // Bradford chromatic adaptation from D65 to D50 + // The matrix below is the result of three operations: + // - convert from XYZ to retinal cone domain + // - scale components from one reference white to another + // - convert back to XYZ + // see https://github.com/LeaVerou/color.js/pull/354/files + let M = [ + [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], + [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], + [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371], + ]; + return multiplyMatrices(M, [x, y, z]).concat(alpha == null || alpha == 1 ? [] : [alpha]); } - function parseDimension(name) { - let index = name.length; - while (index--) { - if (isLetter(name.charCodeAt(index))) { - continue; - } - index++; - break; + function xyzd502srgb(x, y, z, alpha = null) { + // @ts-ignore + return lsrgb2srgbvalues( + /* r: */ + x * 3.1341359569958707 - y * 1.6173863321612538 - 0.4906619460083532 * z, + /* g: */ + x * -0.978795502912089 + y * 1.916254567259524 + 0.03344273116131949 * z, + /* b: */ + x * 0.07195537988411677 - y * 0.2289768264158322 + 1.405386058324125 * z, alpha); + } + + function XYZ_to_lin_sRGB(x, y, z, alpha = null) { + // convert XYZ to linear-light sRGB + const M = [ + [12831 / 3959, -329 / 214, -1974 / 3959], + [-851781 / 878810, 1648619 / 878810, 36519 / 878810], + [705 / 12673, -2585 / 12673, 705 / 667], + ]; + const XYZ = [x, y, z]; // convert to XYZ + return multiplyMatrices(M, XYZ) + .map((v) => v) + .concat(alpha == null || alpha == 1 ? [] : [alpha]); + } + function XYZ_D50_to_D65(x, y, z) { + // Bradford chromatic adaptation from D50 to D65 + const M = [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753], + ]; + const XYZ = [x, y, z]; + return multiplyMatrices(M, XYZ); + } + // xyz d65 + function srgb2xyz(r, g, b, alpha) { + [r, g, b] = srgb2lsrgbvalues(r, g, b); + // xyx d65 + let rgb = [ + 0.4123907992659595 * r + 0.35758433938387796 * g + 0.1804807884018343 * b, + 0.21263900587151036 * r + 0.7151686787677559 * g + 0.07219231536073371 * b, + 0.01933081871559185 * r + 0.11919477979462599 * g + 0.9505321522496606 * b, + ]; + if (alpha != null && alpha != 1) { + rgb.push(alpha); } - if (index < 0) { - return null; + return rgb; + } + // xyz d50 + function srgb2xyz_d65(r, g, b, alpha) { + // xyx d65 + // @ts-ignore + let rgb = XYZ_D65_to_D50(...srgb2xyz(r, g, b)); + if (alpha != null && alpha != 1) { + rgb.push(alpha); } - const dimension = { - typ: exports.EnumToken.DimensionTokenType, - val: +name.slice(0, index), - unit: name.slice(index), - }; - if (Number.isNaN(dimension.val)) { + return rgb; + } + + function hex2oklchToken(token) { + const values = hex2oklchvalues(token); + return oklchToken(values); + } + function rgb2oklchToken(token) { + const values = rgb2oklchvalues(token); + if (values == null) { return null; } - if (isAngle(dimension)) { - // @ts-ignore - dimension.typ = exports.EnumToken.AngleTokenType; - } - else if (isLength(dimension)) { - // @ts-ignore - dimension.typ = exports.EnumToken.LengthTokenType; - } - else if (isTime(dimension)) { - // @ts-ignore - dimension.typ = exports.EnumToken.TimeTokenType; + return oklchToken(values); + } + function hsl2oklchToken(token) { + const values = hsl2oklchvalues(token); + if (values == null) { + return null; } - else if (isResolution(dimension)) { - // @ts-ignore - dimension.typ = exports.EnumToken.ResolutionTokenType; - if (dimension.unit == "dppx") { - dimension.unit = "x"; - } + return oklchToken(values); + } + function hwb2oklchToken(token) { + const values = hwb2oklchvalues(token); + if (values == null) { + return null; } - else if (isFrequency(dimension)) { - // @ts-ignore - dimension.typ = exports.EnumToken.FrequencyTokenType; + return oklchToken(values); + } + function cmyk2oklchToken(token) { + const values = cmyk2oklchvalues(token); + if (values == null) { + return null; } - else if (isFlex(dimension)) { - // @ts-ignore - dimension.typ = exports.EnumToken.FlexTokenType; + return oklchToken(values); + } + function lab2oklchToken(token) { + const values = lab2oklchvalues(token); + if (values == null) { + return null; } - return dimension; + return oklchToken(values); } - function isHexColor(name) { - if (name.charAt(0) != "#" || ![4, 5, 7, 9].includes(name.length)) { - return false; + function oklab2oklchToken(token) { + const values = oklab2oklchvalues(token); + if (values == null) { + return null; } - for (let chr of name.slice(1)) { - let codepoint = chr.charCodeAt(0); - if (!isDigit(codepoint) && - // A-F - !(codepoint >= 0x41 && codepoint <= 0x46) && - // a-f - !(codepoint >= 0x61 && codepoint <= 0x66)) { - return false; - } + return oklchToken(values); + } + function lch2oklchToken(token) { + const values = lch2oklchvalues(token); + if (values == null) { + return null; } - return true; + return oklchToken(values); } - function isFunction(name) { - return name.endsWith("(") && isIdent(name.slice(0, -1)); + function color2oklchToken(token) { + const values = color2srgbvalues(token); + if (values == null) { + return null; + } + // @ts-ignore + return oklchToken(srgb2oklch(...values)); } - function isNewLine(codepoint) { - // \n \r \f \v - return (codepoint == 0xa || - codepoint == 0xb || - codepoint == 0xc || - codepoint == 0xd || - codepoint == 0x2028 || - codepoint == 0x2029); + function oklchToken(values) { + values[2] = toPrecisionAngle(values[2]); + const chi = [ + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) }, + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) }, + { typ: exports.EnumToken.NumberTokenType, val: values[2] }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { + typ: exports.EnumToken.PercentageTokenType, + val: toPrecisionValue(values[3], 2) * 100, + }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: "oklch", + chi, + kin: exports.ColorType.OKLCH, + }; } - function isWhiteSpace(codepoint) { - return (codepoint == 0x9 || - codepoint == 0x20 || - // isNewLine - codepoint == 0xa || - codepoint == 0xb || - codepoint == 0xc || - codepoint == 0xd || - codepoint == 0x2028 || - codepoint == 0x2029); + function hex2oklchvalues(token) { + // @ts-ignore + return labvalues2lchvalues(...hex2oklabvalues(token)); } - function isValue(token) { - if (token == null) { - return false; + function rgb2oklchvalues(token) { + const values = rgb2oklabvalues(token); + if (values == null) { + return null; } - return (token.typ === exports.EnumToken.IdenTokenType || - token.typ === exports.EnumToken.DimensionTokenType || - token.typ === exports.EnumToken.LengthTokenType || - token.typ === exports.EnumToken.AngleTokenType || - token.typ === exports.EnumToken.FlexTokenType || - token.typ === exports.EnumToken.TimeTokenType || - token.typ === exports.EnumToken.ResolutionTokenType || - token.typ === exports.EnumToken.FrequencyTokenType || - token.typ === exports.EnumToken.NumberTokenType || - token.typ === exports.EnumToken.ColorTokenType || - token.typ === exports.EnumToken.FunctionTokenType || - token.typ === exports.EnumToken.UrlFunctionTokenType || - token.typ === exports.EnumToken.GridTemplateFuncTokenType || - token.typ === exports.EnumToken.ImageFunctionTokenType || - token.typ === exports.EnumToken.TimelineFunctionTokenType || - token.typ === exports.EnumToken.TimingFunctionTokenType || - token.typ === exports.EnumToken.MathFunctionTokenType || - token.typ === exports.EnumToken.TransformFunctionTokenType); + // @ts-ignore + return labvalues2lchvalues(...values); } - - function getComponents(token) { - if (token.typ === exports.EnumToken.IdenTokenType) { - if (isColor(token)) { - parseColor(token); - } - else { - return null; - } - } - if (token.kin == exports.ColorType.HEX || token.kin == exports.ColorType.LIT) { - const value = expandHexValue(token.kin == exports.ColorType.LIT ? COLORS_NAMES[token.val.toLowerCase()] : token.val); - // @ts-ignore - return value - .slice(1) - .match(/([a-fA-F0-9]{2})/g) - .map((t, index) => { - return { typ: exports.EnumToken.Number, val: index < 3 ? parseInt(t, 16) : parseInt(t, 16) / 255 }; - }); + function hsl2oklchvalues(token) { + // @ts-ignore + return labvalues2lchvalues(...hsl2oklabvalues(token)); + } + function hwb2oklchvalues(token) { + // @ts-ignore + return labvalues2lchvalues(...hwb2oklabvalues(token)); + } + function cmyk2oklchvalues(token) { + const values = cmyk2srgbvalues(token); + // @ts-ignore + return values == null ? null : srgb2oklch(...values); + } + function lab2oklchvalues(token) { + const values = lab2oklabvalues(token); + if (values == null) { + return null; } - const result = []; - for (const child of token.chi) { - if ([ - exports.EnumToken.LiteralTokenType, - exports.EnumToken.CommentTokenType, - exports.EnumToken.CommaTokenType, - exports.EnumToken.WhitespaceTokenType, - ].includes(child.typ)) { - continue; - } - if (child.typ === exports.EnumToken.IdenTokenType && isColor(child)) { - parseColor(child); - } - if (child.typ === exports.EnumToken.FunctionTokenType || - child.typ === exports.EnumToken.WildCardFunctionTokenType || - child.typ === exports.EnumToken.MathFunctionTokenType) { - if ("var" == child.val.toLowerCase()) { - return null; - } - else { - for (const { value } of walkValues(child.chi)) { - if (value.typ == exports.EnumToken.WildCardFunctionTokenDefType && - "var" === value.val.toLowerCase()) { - return null; - } - } - } - } - if (child.typ == exports.EnumToken.ColorTokenType && "currentcolor" === child.val.toLowerCase()) { - return null; - } - result.push(child); + // @ts-ignore + return labvalues2lchvalues(...values); + } + function lch2oklchvalues(token) { + const values = lch2oklabvalues(token); + if (values == null) { + return null; } - return result; + // @ts-ignore + return labvalues2lchvalues(...values); } - - // from https://www.w3.org/TR/css-color-4/multiply-matrices.js - /** - * Simple matrix (and vector) multiplication - * Warning: No error handling for incompatible dimensions! - * @author Lea Verou 2020 MIT License - */ - // A is m x n. B is n x p. product is m x p. - function multiplyMatrices(A, B) { - let m = A.length; - if (!Array.isArray(A[0])) { - // A is vector, convert to [[a, b, c, ...]] - A = [A]; + function oklab2oklchvalues(token) { + const values = getOKLABComponents(token); + if (values == null) { + return null; } - if (!Array.isArray(B[0])) { - // B is vector, convert to [[a], [b], [c], ...]] - B = B.map((x) => [x]); + // @ts-ignore + return labvalues2lchvalues(...values); + } + function srgb2oklch(r, g, blue, alpha) { + // @ts-ignore + return labvalues2lchvalues(...srgb2oklab(r, g, blue, alpha)); + } + function getOKLCHComponents(token) { + const components = getColorComponents(token); + if (components == null) { + return null; } - let p = B[0].length; - let B_cols = B[0].map((_, i) => B.map((x) => x[i])); // transpose B - let product = A.map((row) => B_cols.map((col) => { - if (!Array.isArray(row)) { - return col.reduce((a, c) => a + c * row, 0); + for (let i = 0; i < components.length; i++) { + if (![ + exports.EnumToken.NumberTokenType, + exports.EnumToken.PercentageTokenType, + exports.EnumToken.AngleTokenType, + exports.EnumToken.IdenTokenType, + ].includes(components[i].typ)) { + return []; } - return row.reduce((a, c, i) => a + c * (col[i] || 0), 0); - })); - if (m === 1) { - product = product[0]; // Avoid [[a, b, c, ...]] - } - if (p === 1) { - return product.map((x) => x[0]); // Avoid [[a], [b], [c], ...]] } - return product; + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t); + // @ts-ignore + t = components[1]; + // @ts-ignore + const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 0.4 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const h = getAngle(t) * 360; + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null || (t.typ == exports.EnumToken.IdenTokenType && t.val == "none") ? 1 : getNumber(t); + return [l, c, h, alpha]; } - function hex2lchToken(token) { - const values = hex2lchvalues(token); + function hex2oklabToken(token) { + const values = hex2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function rgb2lchToken(token) { - const values = rgb2lchvalues(token); + function rgb2oklabToken(token) { + const values = rgb2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function hsl2lchToken(token) { - const values = hsl2lchvalues(token); + function hsl2oklabToken(token) { + const values = hsl2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function hwb2lchToken(token) { - const values = hwb2lchvalues(token); + function hwb2oklabToken(token) { + const values = hwb2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function cmyk2lchToken(token) { - const values = cmyk2lchvalues(token); + function cmyk2oklabToken(token) { + const values = cmyk2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function lab2lchToken(token) { - const values = lab2lchvalues(token); + function lab2oklabToken(token) { + const values = lab2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function oklab2lchToken(token) { - const values = oklab2lchvalues(token); + function lch2oklabToken(token) { + const values = lch2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function oklch2lchToken(token) { - const values = oklch2lchvalues(token); + function oklch2oklabToken(token) { + const values = oklch2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function color2lchToken(token) { - const values = color2lchvalues(token); + function color2oklabToken(token) { + const values = color2oklabvalues(token); if (values == null) { return null; } - return lchToken(values); + return oklabToken(values); } - function lchToken(values) { - values[2] = toPrecisionAngle(values[2]); + function oklabToken(values) { const chi = [ { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) }, { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) }, - { typ: exports.EnumToken.NumberTokenType, val: values[2] }, + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[2]) }, ]; if (values.length == 4) { chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { typ: exports.EnumToken.PercentageTokenType, - val: values[3] * 100, + val: toPrecisionValue(values[3], 2) * 100, }); } return { typ: exports.EnumToken.ColorTokenType, - val: "lch", + val: "oklab", chi, - kin: exports.ColorType.LCH, + kin: exports.ColorType.OKLAB, }; } - function hex2lchvalues(token) { - const values = hex2labvalues(token); - // @ts-ignore - return values == null ? null : labvalues2lchvalues(...values); - } - function rgb2lchvalues(token) { - const values = rgb2labvalues(token); - // @ts-ignore - return values == null ? null : labvalues2lchvalues(...values); - } - function hsl2lchvalues(token) { - const values = hsl2labvalues(token); - // @ts-ignore - return values == null ? null : labvalues2lchvalues(...values); - } - function hwb2lchvalues(token) { - const values = hwb2labvalues(token); + function hex2oklabvalues(token) { + const values = hex2srgbvalues(token); + if (values == null) { + return null; + } // @ts-ignore - return values == null ? null : labvalues2lchvalues(...values); + return srgb2oklab(...values); } - function lab2lchvalues(token) { - const values = getLABComponents(token); + function rgb2oklabvalues(token) { + const values = rgb2srgb(token); + if (values == null) { + return null; + } // @ts-ignore - return values == null ? null : labvalues2lchvalues(...values); + return srgb2oklab(...values); } - function srgb2lch(r, g, blue, alpha) { + function hsl2oklabvalues(token) { + const values = hsl2srgb(token); + if (values == null) { + return null; + } // @ts-ignore - return labvalues2lchvalues(...srgb2labvalues(r, g, blue, alpha)); + return srgb2oklab(...values); } - function oklab2lchvalues(token) { - const values = oklab2labvalues(token); + function hwb2oklabvalues(token) { // @ts-ignore - return values == null ? null : labvalues2lchvalues(...values); + return srgb2oklab(...hwb2srgbvalues(token)); } - function cmyk2lchvalues(token) { + function cmyk2oklabvalues(token) { const values = cmyk2srgbvalues(token); // @ts-ignore - return values == null ? null : srgb2lch(...values); + return values == null ? null : srgb2oklab(...values); } - function oklch2lchvalues(token) { - const values = oklch2labvalues(token); + function lab2oklabvalues(token) { + const values = lab2srgbvalues(token); if (values == null) { return null; } // @ts-ignore - return labvalues2lchvalues(...values); + return srgb2oklab(...values); } - function color2lchvalues(token) { - const values = color2srgbvalues(token); - if (values == null) { - return null; - } + function lch2oklabvalues(token) { + const values = lch2srgbvalues(token); // @ts-ignore - return srgb2lch(...values); + return values == null ? null : srgb2oklab(...values); } - function labvalues2lchvalues(l, a, b, alpha = null) { - let c = Math.sqrt(a * a + b * b); - let h = (Math.atan2(b, a) * 180) / Math.PI; - if (h < 0) { - h += 360; - } - if (c < 0.0001) { - c = h = 0; - } - return alpha == null ? [l, c, h] : [l, c, h, alpha]; + function oklch2oklabvalues(token) { + const values = getOKLCHComponents(token); + // @ts-ignore + return values == null ? null : lchvalues2labvalues(...values); } - function xyz2lchvalues(x, y, z, alpha) { - // @ts-ignore( - const lch = labvalues2lchvalues(...xyz2lab(x, y, z)); - return alpha == null || alpha == 1 ? lch : lch.concat(alpha); + function color2oklabvalues(token) { + const values = color2srgbvalues(token); + // @ts-ignore + return values == null ? null : srgb2oklab(...values); } - function getLCHComponents(token) { - const components = getComponents(token); - if (components == null) { + function srgb2oklab(r, g, blue, alpha) { + [r, g, blue] = srgb2lsrgbvalues(r, g, blue); + let L = Math.cbrt(0.41222147079999993 * r + 0.5363325363 * g + 0.0514459929 * blue); + let M = Math.cbrt(0.2119034981999999 * r + 0.6806995450999999 * g + 0.1073969566 * blue); + let S = Math.cbrt(0.08830246189999998 * r + 0.2817188376 * g + 0.6299787005000002 * blue); + const l = 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S; + const a = r == g && g == blue ? 0 : 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S; + const b = r == g && g == blue ? 0 : 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S; + return alpha == null || alpha == 1 ? [l, a, b] : [l, a, b, alpha]; + } + function getOKLABComponents(token) { + const components = getColorComponents(token); + if (components == null || components.length < 3) { return null; } - for (let i = 0; i < components.length; i++) { - if (![ - exports.EnumToken.NumberTokenType, - exports.EnumToken.PercentageTokenType, - exports.EnumToken.AngleTokenType, - exports.EnumToken.IdenTokenType, - ].includes(components[i].typ)) { - return null; - } - } // @ts-ignore let t = components[0]; // @ts-ignore - const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); + const l = getNumber(t); // @ts-ignore t = components[1]; // @ts-ignore - const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); + const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 0.4 : 1); // @ts-ignore t = components[2]; // @ts-ignore - const h = getAngle(t) * 360; - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - return alpha == null ? [l, c, h] : [l, c, h, alpha]; - } - - /* - */ - function xyzd502lch(x, y, z, alpha) { + const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 0.4 : 1); // @ts-ignore - const [l, a, b] = xyz2lab(...XYZ_D50_to_D65(x, y, z)); - // L in range [0,100]. For use in CSS, add a percent - return labvalues2lchvalues(l, a, b, alpha); + let alpha = null; + if (components.length > 3) { + alpha = getNumber(components[3]); + } + const rgb = [l, a, b]; + if (alpha != 1 && alpha != null) { + rgb.push(alpha); + } + return rgb; } - function XYZ_D65_to_D50(x, y, z, alpha = null) { - // Bradford chromatic adaptation from D65 to D50 - // The matrix below is the result of three operations: - // - convert from XYZ to retinal cone domain - // - scale components from one reference white to another - // - convert back to XYZ - // see https://github.com/LeaVerou/color.js/pull/354/files - let M = [ - [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], - [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], - [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371], + function OKLab_to_XYZ(l, a, b, alpha = null) { + // Given OKLab, convert to XYZ relative to D65 + const LMStoXYZ = [ + [1.2268798758459243, -0.5578149944602171, 0.2813910456659647], + [-0.0405757452148008, 1.112286803280317, -0.0717110580655164], + [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816], ]; - return multiplyMatrices(M, [x, y, z]).concat(alpha == null || alpha == 1 ? [] : [alpha]); + const OKLabtoLMS = [ + [1.0, 0.3963377773761749, 0.2158037573099136], + [1.0, -0.1055613458156586, -0.0638541728258133], + [1.0, -0.0894841775298119, -1.2914855480194092], + ]; + const LMSnl = multiplyMatrices(OKLabtoLMS, [l, a, b]); + const xyz = multiplyMatrices(LMStoXYZ, LMSnl.map((c) => c ** 3)); + if (alpha != null) { + xyz.push(alpha); + } + return xyz; } - function xyzd502srgb(x, y, z, alpha = null) { - // @ts-ignore + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + function OKLab_to_sRGB(l, a, b) { + let L = Math.pow(l * 0.99999999845051981432 + 0.39633779217376785678 * a + 0.21580375806075880339 * b, 3); + let M = Math.pow(l * 1.0000000088817607767 - 0.1055613423236563494 * a - 0.063854174771705903402 * b, 3); + let S = Math.pow(l * 1.0000000546724109177 - 0.089484182094965759684 * a - 1.2914855378640917399 * b, 3); return lsrgb2srgbvalues( /* r: */ - x * 3.1341359569958707 - y * 1.6173863321612538 - 0.4906619460083532 * z, + 4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S, /* g: */ - x * -0.978795502912089 + y * 1.916254567259524 + 0.03344273116131949 * z, - /* b: */ - x * 0.07195537988411677 - y * 0.2289768264158322 + 1.405386058324125 * z, alpha); + -1.2684380040921763 * L + 2.6097574006633715 * M - 0.3413193963102197 * S, + /* b: */ + -0.004196086541837188 * L - 0.7034186144594493 * M + 1.7076147009309444 * S); } - function XYZ_to_lin_sRGB(x, y, z, alpha = null) { - // convert XYZ to linear-light sRGB - const M = [ - [12831 / 3959, -329 / 214, -1974 / 3959], - [-851781 / 878810, 1648619 / 878810, 36519 / 878810], - [705 / 12673, -2585 / 12673, 705 / 667], - ]; - const XYZ = [x, y, z]; // convert to XYZ - return multiplyMatrices(M, XYZ) - .map((v) => v) - .concat(alpha == null || alpha == 1 ? [] : [alpha]); - } - function XYZ_D50_to_D65(x, y, z) { - // Bradford chromatic adaptation from D50 to D65 - const M = [ - [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], - [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], - [0.012314001688319899, -0.020507696433477912, 1.3303659366080753], - ]; - const XYZ = [x, y, z]; - return multiplyMatrices(M, XYZ); - } - // xyz d65 - function srgb2xyz(r, g, b, alpha) { - [r, g, b] = srgb2lsrgbvalues(r, g, b); - // xyx d65 - let rgb = [ - 0.4123907992659595 * r + 0.35758433938387796 * g + 0.1804807884018343 * b, - 0.21263900587151036 * r + 0.7151686787677559 * g + 0.07219231536073371 * b, - 0.01933081871559185 * r + 0.11919477979462599 * g + 0.9505321522496606 * b, - ]; - if (alpha != null && alpha != 1) { - rgb.push(alpha); - } - return rgb; + function hex2labToken(token) { + const values = hex2labvalues(token); + return values == null ? null : labToken(values); } - // xyz d50 - function srgb2xyz_d50(r, g, b, alpha) { - // xyx d65 - // @ts-ignore - let rgb = XYZ_D65_to_D50(...srgb2xyz(r, g, b)); - if (alpha != null && alpha != 1) { - rgb.push(alpha); + function rgb2labToken(token) { + const values = rgb2labvalues(token); + if (values == null) { + return null; } - return rgb; - } - - function hex2oklchToken(token) { - const values = hex2oklchvalues(token); - return oklchToken(values); + return labToken(values); } - function rgb2oklchToken(token) { - const values = rgb2oklchvalues(token); + function hsl2labToken(token) { + const values = hsl2labvalues(token); if (values == null) { return null; } - return oklchToken(values); - } - function hsl2oklchToken(token) { - const values = hsl2oklchvalues(token); - if (values == null) { - return null; - } - return oklchToken(values); + return labToken(values); } - function hwb2oklchToken(token) { - const values = hwb2oklchvalues(token); + function hwb2labToken(token) { + const values = hwb2labvalues(token); if (values == null) { return null; } - return oklchToken(values); + return labToken(values); } - function cmyk2oklchToken(token) { - const values = cmyk2oklchvalues(token); + function cmyk2labToken(token) { + const values = cmyk2labvalues(token); if (values == null) { return null; } - return oklchToken(values); + return labToken(values); } - function lab2oklchToken(token) { - const values = lab2oklchvalues(token); + function lch2labToken(token) { + const values = lch2labvalues(token); if (values == null) { return null; } - return oklchToken(values); + return labToken(values); } - function oklab2oklchToken(token) { - const values = oklab2oklchvalues(token); + function oklab2labToken(token) { + const values = oklab2labvalues(token); if (values == null) { return null; } - return oklchToken(values); + return labToken(values); } - function lch2oklchToken(token) { - const values = lch2oklchvalues(token); + function oklch2labToken(token) { + const values = oklch2labvalues(token); if (values == null) { return null; } - return oklchToken(values); + return labToken(values); } - function color2oklchToken(token) { - const values = color2srgbvalues(token); + function color2labToken(token) { + const values = color2labvalues(token); if (values == null) { return null; } - // @ts-ignore - return oklchToken(srgb2oklch(...values)); + return labToken(values); } - function oklchToken(values) { - values[2] = toPrecisionAngle(values[2]); + function labToken(values) { const chi = [ { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) }, { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) }, - { typ: exports.EnumToken.NumberTokenType, val: values[2] }, + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[2]) }, ]; if (values.length == 4) { chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { typ: exports.EnumToken.PercentageTokenType, - val: values[3] * 100, + val: toPrecisionValue(values[3], 2) * 100, }); } return { typ: exports.EnumToken.ColorTokenType, - val: "oklch", + val: "lab", chi, - kin: exports.ColorType.OKLCH, + kin: exports.ColorType.LAB, }; } - function hex2oklchvalues(token) { + // L: 0% = 0.0, 100% = 100.0 + // for a and b: -100% = -125, 100% = 125 + function hex2labvalues(token) { + const values = hex2srgbvalues(token); // @ts-ignore - return labvalues2lchvalues(...hex2oklabvalues(token)); + return values == null ? null : srgb2labvalues(...values); } - function rgb2oklchvalues(token) { - const values = rgb2oklabvalues(token); + function rgb2labvalues(token) { + const values = rgb2srgb(token); + // @ts-ignore + return values == null ? null : srgb2labvalues(...values); + } + function cmyk2labvalues(token) { + const values = cmyk2srgbvalues(token); + // @ts-ignore + return values == null ? null : srgb2labvalues(...values); + } + function hsl2labvalues(token) { + const values = hsl2srgb(token); if (values == null) { return null; } // @ts-ignore - return labvalues2lchvalues(...values); - } - function hsl2oklchvalues(token) { - // @ts-ignore - return labvalues2lchvalues(...hsl2oklabvalues(token)); + return srgb2labvalues(...values); } - function hwb2oklchvalues(token) { + function hwb2labvalues(token) { + const values = hwb2srgbvalues(token); + if (values == null) { + return null; + } // @ts-ignore - return labvalues2lchvalues(...hwb2oklabvalues(token)); + return srgb2labvalues(...values); } - function cmyk2oklchvalues(token) { - const values = cmyk2srgbvalues(token); + function lch2labvalues(token) { + const values = getLCHComponents(token); // @ts-ignore - return values == null ? null : srgb2oklch(...values); + return values == null ? null : lchvalues2labvalues(...values); } - function lab2oklchvalues(token) { - const values = lab2oklabvalues(token); + function oklab2labvalues(token) { + const values = getOKLABComponents(token); if (values == null) { return null; } // @ts-ignore - return labvalues2lchvalues(...values); + return xyz2lab(...XYZ_D65_to_D50(...OKLab_to_XYZ(...values))); } - function lch2oklchvalues(token) { - const values = lch2oklabvalues(token); + function oklch2labvalues(token) { + const values = oklch2srgbvalues(token); if (values == null) { return null; } // @ts-ignore - return labvalues2lchvalues(...values); + return srgb2labvalues(...values); } - function oklab2oklchvalues(token) { - const values = getOKLABComponents(token); - if (values == null) { + function color2labvalues(token) { + const val = color2srgbvalues(token); + if (val == null) { return null; } // @ts-ignore - return labvalues2lchvalues(...values); + return srgb2labvalues(...val); } - function srgb2oklch(r, g, blue, alpha) { - // @ts-ignore - return labvalues2lchvalues(...srgb2oklab(r, g, blue, alpha)); + function srgb2labvalues(r, g, b, a) { + // @ts-ignore */ + const result = xyz2lab(...srgb2xyz_d65(r, g, b)); + // Fixes achromatic RGB colors having a _slight_ chroma due to floating-point errors + // and approximated computations in sRGB <-> CIELab. + // See: https://github.com/d3/d3-color/pull/46 + if (r === b && b === g) { + result[1] = result[2] = 0; + } + if (a != null) { + result.push(a); + } + return result; } - function getOKLCHComponents(token) { - const components = getComponents(token); + function xyz2lab(x, y, z, a = null) { + // Assuming XYZ is relative to D50, convert to CIE Lab + // from CIE standard, which now defines these as a rational fraction + // var e = 216/24389; // 6^3/29^3 + // var k = 24389/27; // 29^3/3^3 + // compute xyz, which is XYZ scaled relative to reference white + const xyz = [x, y, z].map((value, i) => value / D50[i]); + // now compute f + const f = xyz.map((value) => (value > e ? Math.cbrt(value) : (k * value + 16) / 116)); + const result = [ + 116 * f[1] - 16, // L + 500 * (f[0] - f[1]), // a + 200 * (f[1] - f[2]), // b + ]; + // L in range [0,100]. For use in CSS, add a percent + if (a != null && a != 1) { + result.push(a); + } + return result; + } + function lchvalues2labvalues(l, c, h, a = null) { + // l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180 + const result = [l, c * Math.cos((h * Math.PI) / 180), c * Math.sin((h * Math.PI) / 180)]; + if (a != null) { + result.push(a); + } + return result; + } + function getLABComponents(token) { + const components = getColorComponents(token); if (components == null) { return null; } @@ -8542,1041 +8136,615 @@ exports.EnumToken.AngleTokenType, exports.EnumToken.IdenTokenType, ].includes(components[i].typ)) { - return []; + return null; } } // @ts-ignore let t = components[0]; // @ts-ignore - const l = getNumber(t); + const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); // @ts-ignore t = components[1]; // @ts-ignore - const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 0.4 : 1); + const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore t = components[2]; // @ts-ignore - const h = getAngle(t) * 360; + const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); // @ts-ignore t = components[3]; // @ts-ignore - const alpha = t == null || (t.typ == exports.EnumToken.IdenTokenType && t.val == "none") ? 1 : getNumber(t); - return [l, c, h, alpha]; - } - - function hex2oklabToken(token) { - const values = hex2oklabvalues(token); - if (values == null) { - return null; - } - return oklabToken(values); - } - function rgb2oklabToken(token) { - const values = rgb2oklabvalues(token); - if (values == null) { - return null; - } - return oklabToken(values); - } - function hsl2oklabToken(token) { - const values = hsl2oklabvalues(token); - if (values == null) { - return null; - } - return oklabToken(values); - } - function hwb2oklabToken(token) { - const values = hwb2oklabvalues(token); - if (values == null) { - return null; + const alpha = t == null ? 1 : getNumber(t); + const result = [l, a, b]; + if (alpha != null && alpha != 1) { + result.push(alpha); } - return oklabToken(values); + return result; } - function cmyk2oklabToken(token) { - const values = cmyk2oklabvalues(token); - if (values == null) { - return null; - } - return oklabToken(values); + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + // D50 LAB + function Lab_to_sRGB(l, a, b) { + const xyz_d50 = Lab_to_XYZ(l, a, b); + // @ts-ignore + const xyz_d65 = XYZ_D50_to_D65(...xyz_d50); + // @ts-ignore + return xyz2srgb(...xyz_d65); } - function lab2oklabToken(token) { - const values = lab2oklabvalues(token); - if (values == null) { - return null; - } - return oklabToken(values); + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + function Lab_to_XYZ(l, a, b) { + // Convert Lab to D50-adapted XYZ + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + const k = 24389 / 27; // 29^3/3^3 + const e = 216 / 24389; // 6^3/29^3 + const f = []; + // compute f, starting with the luminance-related term + f[1] = (l + 16) / 116; + f[0] = a / 500 + f[1]; + f[2] = f[1] - b / 200; + // compute xyz + const xyz = [ + Math.pow(f[0], 3) > e ? Math.pow(f[0], 3) : (116 * f[0] - 16) / k, + l > k * e ? Math.pow((l + 16) / 116, 3) : l / k, + Math.pow(f[2], 3) > e ? Math.pow(f[2], 3) : (116 * f[2] - 16) / k, + ]; + // Compute XYZ by scaling xyz by reference white + return xyz.map((value, i) => value * D50[i]); } - function lch2oklabToken(token) { - const values = lch2oklabvalues(token); - if (values == null) { - return null; + + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + // srgb-linear -> srgb + // 0 <= r, g, b <= 1 + function srgbvalues(token) { + if (token.typ === exports.EnumToken.IdenTokenType) { + token = parseColor(token); } - return oklabToken(values); - } - function oklch2oklabToken(token) { - const values = oklch2oklabvalues(token); - if (values == null) { - return null; - } - return oklabToken(values); - } - function color2oklabToken(token) { - const values = color2oklabvalues(token); - if (values == null) { - return null; + switch (token.kin) { + case exports.ColorType.LIT: + case exports.ColorType.HEX: + return hex2srgbvalues(token); + case exports.ColorType.RGB: + case exports.ColorType.RGBA: + return rgb2srgb(token); + case exports.ColorType.HSL: + case exports.ColorType.HSLA: + return hsl2srgb(token); + case exports.ColorType.HWB: + return hwb2srgbvalues(token); + case exports.ColorType.LAB: + return lab2srgbvalues(token); + case exports.ColorType.LCH: + return lch2srgbvalues(token); + case exports.ColorType.OKLAB: + return oklab2srgbvalues(token); + case exports.ColorType.OKLCH: + return oklch2srgbvalues(token); + case exports.ColorType.COLOR: + return color2srgbvalues(token); } - return oklabToken(values); + return null; } - function oklabToken(values) { - const chi = [ - { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) }, - { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) }, - { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[2]) }, - ]; - if (values.length == 4) { - chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { - typ: exports.EnumToken.PercentageTokenType, - val: values[3] * 100, - }); - } - return { - typ: exports.EnumToken.ColorTokenType, - val: "oklab", - chi, - kin: exports.ColorType.OKLAB, - }; + function rgb2srgb(token) { + return (getColorComponents(token)?.map?.((t, index) => index == 3 + ? t.typ == exports.EnumToken.IdenTokenType && t.val == "none" + ? 1 + : getNumber(t) + : ((t.typ == exports.EnumToken.PercentageTokenType ? 255 : 1) * + getNumber(t)) / + 255) ?? null); } - function hex2oklabvalues(token) { - const values = hex2srgbvalues(token); - if (values == null) { - return null; - } - // @ts-ignore - return srgb2oklab(...values); + function rgb2srgbvalues(token) { + return (getColorComponents(token)?.map?.((t, index) => index == 3 + ? getNumber(t) + : getNumber(t) / 255) ?? null); } - function rgb2oklabvalues(token) { - const values = rgb2srgb(token); - if (values == null) { - return null; + function hex2srgbvalues(token) { + const value = expandHexValue(token.kin == exports.ColorType.LIT ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb = []; + for (let i = 1; i < value.length; i += 2) { + rgb.push(parseInt(value.slice(i, i + 2), 16) / 255); } - // @ts-ignore - return srgb2oklab(...values); - } - function hsl2oklabvalues(token) { - const values = hsl2srgb(token); - if (values == null) { - return null; + if (rgb.length == 4) { + rgb[3] = +rgb[3].toFixed(2); } - // @ts-ignore - return srgb2oklab(...values); + return rgb; } - function hwb2oklabvalues(token) { + // xyz d65 input + function xyz2srgb(x, y, z, alpha = null) { // @ts-ignore - return srgb2oklab(...hwb2srgbvalues(token)); + return lsrgb2srgbvalues(...XYZ_to_lin_sRGB(x, y, z, alpha)); } - function cmyk2oklabvalues(token) { - const values = cmyk2srgbvalues(token); - // @ts-ignore - return values == null ? null : srgb2oklab(...values); + function hwb2srgbvalues(token) { + const { h: hue, s: white, l: black, a: alpha } = hslvalues(token) ?? {}; + if (hue == null || white == null || black == null) { + return []; + } + const rgb = hslvalues2srgbvalues(hue, 1, 0.5); + for (let i = 0; i < 3; i++) { + rgb[i] *= 1 - white - black; + rgb[i] = rgb[i] + white; + } + if (alpha != null && alpha != 1) { + rgb.push(alpha); + } + return rgb; } - function lab2oklabvalues(token) { - const values = lab2srgbvalues(token); - if (values == null) { + function hsl2srgb(token) { + let { h, s, l, a } = hslvalues(token) ?? {}; + if (h == null || s == null || l == null) { return null; } - // @ts-ignore - return srgb2oklab(...values); - } - function lch2oklabvalues(token) { - const values = lch2srgbvalues(token); - // @ts-ignore - return values == null ? null : srgb2oklab(...values); - } - function oklch2oklabvalues(token) { - const values = getOKLCHComponents(token); - // @ts-ignore - return values == null ? null : lchvalues2labvalues(...values); - } - function color2oklabvalues(token) { - const values = color2srgbvalues(token); - // @ts-ignore - return values == null ? null : srgb2oklab(...values); - } - function srgb2oklab(r, g, blue, alpha) { - [r, g, blue] = srgb2lsrgbvalues(r, g, blue); - let L = Math.cbrt(0.41222147079999993 * r + 0.5363325363 * g + 0.0514459929 * blue); - let M = Math.cbrt(0.2119034981999999 * r + 0.6806995450999999 * g + 0.1073969566 * blue); - let S = Math.cbrt(0.08830246189999998 * r + 0.2817188376 * g + 0.6299787005000002 * blue); - const l = 0.2104542553 * L + 0.793617785 * M - 0.0040720468 * S; - const a = r == g && g == blue ? 0 : 1.9779984951 * L - 2.428592205 * M + 0.4505937099 * S; - const b = r == g && g == blue ? 0 : 0.0259040371 * L + 0.7827717662 * M - 0.808675766 * S; - return alpha == null || alpha == 1 ? [l, a, b] : [l, a, b, alpha]; + return hslvalues2srgbvalues(h, s, l, a); } - function getOKLABComponents(token) { - const components = getComponents(token); - if (components == null || components.length < 3) { + function cmyk2srgbvalues(token) { + const components = getColorComponents(token); + if (components == null) { return null; } // @ts-ignore let t = components[0]; // @ts-ignore - const l = getNumber(t); + const c = getNumber(t); // @ts-ignore t = components[1]; // @ts-ignore - const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 0.4 : 1); + const m = getNumber(t); // @ts-ignore t = components[2]; // @ts-ignore - const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 0.4 : 1); + const y = getNumber(t); // @ts-ignore - let alpha = null; - if (components.length > 3) { - alpha = getNumber(components[3]); - } - const rgb = [l, a, b]; - if (alpha != 1 && alpha != null) { - rgb.push(alpha); - } - return rgb; - } - function OKLab_to_XYZ(l, a, b, alpha = null) { - // Given OKLab, convert to XYZ relative to D65 - const LMStoXYZ = [ - [1.2268798758459243, -0.5578149944602171, 0.2813910456659647], - [-0.0405757452148008, 1.112286803280317, -0.0717110580655164], - [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816], - ]; - const OKLabtoLMS = [ - [1.0, 0.3963377773761749, 0.2158037573099136], - [1.0, -0.1055613458156586, -0.0638541728258133], - [1.0, -0.0894841775298119, -1.2914855480194092], + t = components[3]; + // @ts-ignore + const k = getNumber(t); + const rgb = [ + 1 - Math.min(1, c * (1 - k) + k), + 1 - Math.min(1, m * (1 - k) + k), + 1 - Math.min(1, y * (1 - k) + k), ]; - const LMSnl = multiplyMatrices(OKLabtoLMS, [l, a, b]); - const xyz = multiplyMatrices(LMStoXYZ, LMSnl.map((c) => c ** 3)); - if (alpha != null) { - xyz.push(alpha); + if (components.length == 5) { + rgb.push(getNumber(components[4])); } - return xyz; - } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - function OKLab_to_sRGB(l, a, b) { - let L = Math.pow(l * 0.99999999845051981432 + 0.39633779217376785678 * a + 0.21580375806075880339 * b, 3); - let M = Math.pow(l * 1.0000000088817607767 - 0.1055613423236563494 * a - 0.063854174771705903402 * b, 3); - let S = Math.pow(l * 1.0000000546724109177 - 0.089484182094965759684 * a - 1.2914855378640917399 * b, 3); - return lsrgb2srgbvalues( - /* r: */ - 4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S, - /* g: */ - -1.2684380040921763 * L + 2.6097574006633715 * M - 0.3413193963102197 * S, - /* b: */ - -0.004196086541837188 * L - 0.7034186144594493 * M + 1.7076147009309444 * S); - } - - function hex2labToken(token) { - const values = hex2labvalues(token); - return values == null ? null : labToken(values); + return rgb; } - function rgb2labToken(token) { - const values = rgb2labvalues(token); - if (values == null) { + function oklab2srgbvalues(token) { + const [l, a, b, alpha] = getOKLABComponents(token) ?? []; + if (l == null || a == null || b == null) { return null; } - return labToken(values); - } - function hsl2labToken(token) { - const values = hsl2labvalues(token); - if (values == null) { - return null; + const rgb = OKLab_to_sRGB(l, a, b); + if (alpha != null && alpha != 1) { + rgb.push(alpha); } - return labToken(values); + return rgb; } - function hwb2labToken(token) { - const values = hwb2labvalues(token); - if (values == null) { + function oklch2srgbvalues(token) { + const [l, c, h, alpha] = getOKLCHComponents(token) ?? []; + if (l == null || c == null || h == null) { return null; } - return labToken(values); - } - function cmyk2labToken(token) { - const values = cmyk2labvalues(token); - if (values == null) { - return null; + // @ts-ignore + const rgb = OKLab_to_sRGB(...lchvalues2labvalues(l, c, h)); + if (alpha != 1) { + rgb.push(alpha); } - return labToken(values); + return rgb; } - function lch2labToken(token) { - const values = lch2labvalues(token); - if (values == null) { + function hslvalues(token) { + const components = getColorComponents(token); + if (components == null) { return null; } - return labToken(values); - } - function oklab2labToken(token) { - const values = oklab2labvalues(token); - if (values == null) { - return null; + let t; + // @ts-ignore + let h = getAngle(components[0]); + // @ts-ignore + t = components[1]; + // @ts-ignore + let s = getNumber(t); + // @ts-ignore + t = components[2]; + // @ts-ignore + let l = getNumber(t); + let a = null; + if (components.length == 4) { + // @ts-ignore + t = components[3]; + // @ts-ignore + a = getNumber(t); } - return labToken(values); + return a == null ? { h, s, l } : { h, s, l, a }; } - function oklch2labToken(token) { - const values = oklch2labvalues(token); - if (values == null) { + function hslvalues2srgbvalues(h, s, l, a = null) { + let v = l <= 0.5 ? l * (1.0 + s) : l + s - l * s; + let r = l; + let g = l; + let b = l; + if (v > 0) { + let m = l + l - v; + let sv = (v - m) / v; + h *= 6.0; + let sextant = Math.floor(h); + let fract = h - sextant; + let vsf = v * sv * fract; + let mid1 = m + vsf; + let mid2 = v - vsf; + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + const values = [r, g, b]; + if (a != null && a != 1) { + values.push(a); + } + return values; + } + function lab2srgbvalues(token) { + const [l, a, b, alpha] = getLABComponents(token) ?? []; + if (l == null || a == null || b == null) { return null; } - return labToken(values); + const rgb = Lab_to_sRGB(l, a, b); + if (alpha != null && alpha < 1) { + rgb.push(alpha); + } + return rgb; } - function color2labToken(token) { - const values = color2labvalues(token); - if (values == null) { + function lch2srgbvalues(token) { + const components = getLCHComponents(token); + if (components == null) { return null; } - return labToken(values); + // @ts-ignore + const [l, a, b, alpha] = lchvalues2labvalues(...components); + if (l == null || a == null || b == null) { + return null; + } + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const rgb = Lab_to_sRGB(l, a, b); + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; } - function labToken(values) { - const chi = [ - { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[0]) }, - { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[1]) }, - { typ: exports.EnumToken.NumberTokenType, val: toPrecisionValue(values[2]) }, - ]; - if (values.length == 4) { - chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { - typ: exports.EnumToken.PercentageTokenType, - val: values[3] * 100, - }); + // sRGB -> lRGB + function srgb2lsrgbvalues(r, g, b, a = null) { + // convert an array of linear-light sRGB values in the range 0.0-1.0 + // to gamma corrected form + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // For negative values, linear portion extends on reflection + // of axis, then uses reflected pow below that + const rgb = [r, g, b].map((val) => { + const abs = Math.abs(val); + if (abs <= 0.04045) { + return val / 12.92; + } + return (Math.sign(val) || 1) * Math.pow((abs + 0.055) / 1.055, 2.4); + }); + if (a != 1 && a != null) { + rgb.push(a); } - return { - typ: exports.EnumToken.ColorTokenType, - val: "lab", - chi, - kin: exports.ColorType.LAB, - }; + return rgb; } - // L: 0% = 0.0, 100% = 100.0 - // for a and b: -100% = -125, 100% = 125 - function hex2labvalues(token) { - const values = hex2srgbvalues(token); - // @ts-ignore - return values == null ? null : srgb2labvalues(...values); + function lsrgb2srgbvalues(r, g, b, alpha = null) { + // convert an array of linear-light sRGB values in the range 0.0-1.0 + // to gamma corrected form + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // For negative values, linear portion extends on reflection + // of axis, then uses reflected pow below that + const rgb = [r, g, b].map((val) => { + let abs = Math.abs(val); + if (Math.abs(val) > 0.0031308) { + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); + } + return 12.92 * val; + }); + if (alpha != 1 && alpha != null) { + rgb.push(alpha); + } + return rgb; } - function rgb2labvalues(token) { - const values = rgb2srgb(token); - // @ts-ignore - return values == null ? null : srgb2labvalues(...values); + + function toHexString(acc, value) { + return acc + value.toString(16).padStart(2, "0"); } - function cmyk2labvalues(token) { - const values = cmyk2srgbvalues(token); - // @ts-ignore - return values == null ? null : srgb2labvalues(...values); + function reduceHexValue(value) { + if (value[0] != "#") { + value = COLORS_NAMES[value.toLowerCase()] ?? value; + } + const named_color = NAMES_COLORS[expandHexValue(value)]; + if (value.length == 7) { + if (value[1] == value[2] && value[3] == value[4] && value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && value[3] == value[4] && value[5] == value[6] && value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7] == "f" ? "" : value[7]}`; + } + if (value.endsWith("ff")) { + value = value.slice(0, -2); + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; } - function hsl2labvalues(token) { - const values = hsl2srgb(token); - if (values == null) { + function expandHexValue(value) { + if (value.length == 4) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; + } + if (value.length == 5) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; + } + return value; + } + function rgb2HexToken(token) { + let value = rgb2hexvalues(token); + if (value == null) { return null; } - // @ts-ignore - return srgb2labvalues(...values); + return hexToken(value); } - function hwb2labvalues(token) { - const values = hwb2srgbvalues(token); - if (values == null) { + function hsl2HexToken(token) { + let value = hsl2hexvalues(token); + if (value == null) { return null; } - // @ts-ignore - return srgb2labvalues(...values); + return hexToken(value); } - function lch2labvalues(token) { - const values = getLCHComponents(token); - // @ts-ignore - return values == null ? null : lchvalues2labvalues(...values); + function cmyk2HexToken(token) { + let value = cmyk2hexvalues(token); + if (value == null) { + return null; + } + return hexToken(value); } - function oklab2labvalues(token) { - const values = getOKLABComponents(token); - if (values == null) { + function hwb2HexToken(token) { + let value = hwb2hexvalues(token); + if (value == null) { return null; } - // @ts-ignore - return xyz2lab(...XYZ_D65_to_D50(...OKLab_to_XYZ(...values))); + return hexToken(value); } - function oklch2labvalues(token) { - const values = oklch2srgbvalues(token); - if (values == null) { + function color2HexToken(token) { + let value = color2srgbvalues(token); + if (value == null) { return null; } - // @ts-ignore - return srgb2labvalues(...values); + return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); } - function color2labvalues(token) { - const val = color2srgbvalues(token); - if (val == null) { + function oklab2HexToken(token) { + let value = oklab2srgbvalues(token); + if (value == null) { return null; } - // @ts-ignore - return srgb2labvalues(...val); + return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); } - function srgb2labvalues(r, g, b, a) { - // @ts-ignore */ - const result = xyz2lab(...srgb2xyz_d50(r, g, b)); - // Fixes achromatic RGB colors having a _slight_ chroma due to floating-point errors - // and approximated computations in sRGB <-> CIELab. - // See: https://github.com/d3/d3-color/pull/46 - if (r === b && b === g) { - result[1] = result[2] = 0; - } - if (a != null) { - result.push(a); + function oklch2HexToken(token) { + let value = oklch2srgbvalues(token); + if (value == null) { + return null; } - return result; + return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); } - function xyz2lab(x, y, z, a = null) { - // Assuming XYZ is relative to D50, convert to CIE Lab - // from CIE standard, which now defines these as a rational fraction - // var e = 216/24389; // 6^3/29^3 - // var k = 24389/27; // 29^3/3^3 - // compute xyz, which is XYZ scaled relative to reference white - const xyz = [x, y, z].map((value, i) => value / D50[i]); - // now compute f - const f = xyz.map((value) => (value > e ? Math.cbrt(value) : (k * value + 16) / 116)); - const result = [ - 116 * f[1] - 16, // L - 500 * (f[0] - f[1]), // a - 200 * (f[1] - f[2]), // b - ]; - // L in range [0,100]. For use in CSS, add a percent - if (a != null && a != 1) { - result.push(a); + function lab2HexToken(token) { + let value = lab2srgbvalues(token); + if (value == null) { + return null; } - return result; + return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); } - function lchvalues2labvalues(l, c, h, a = null) { - // l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180 - const result = [l, c * Math.cos((h * Math.PI) / 180), c * Math.sin((h * Math.PI) / 180)]; - if (a != null) { - result.push(a); + function lch2HexToken(token) { + let value = lch2srgbvalues(token); + if (value == null) { + return null; } - return result; + return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); } - function getLABComponents(token) { - const components = getComponents(token); - if (components == null) { + function hexToken(value) { + value = reduceHexValue(value); + return { + typ: exports.EnumToken.ColorTokenType, + val: value, + kin: value[0] == "#" ? exports.ColorType.HEX : exports.ColorType.LIT, + }; + } + function rgb2hexvalues(token) { + let value = "#"; + let t; + // @ts-ignore + const components = getColorComponents(token); + if (components == null || components.length < 3) { return null; } - for (let i = 0; i < components.length; i++) { - if (![ - exports.EnumToken.NumberTokenType, - exports.EnumToken.PercentageTokenType, - exports.EnumToken.AngleTokenType, - exports.EnumToken.IdenTokenType, - ].includes(components[i].typ)) { - return null; - } - } - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[3]; // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - const result = [l, a, b]; - if (alpha != null && alpha != 1) { - result.push(alpha); + for (let i = 0; i < 3; i++) { + // @ts-ignore + t = components[i]; + value += + ( // @ts-expect-error + t.typ == exports.EnumToken.Iden && t.val == "none" + ? "0" + : Math.round(getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 255 : 1))) + .toString(16) + .padStart(2, "0"); } - return result; - } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - // D50 LAB - function Lab_to_sRGB(l, a, b) { - const xyz_d50 = Lab_to_XYZ(l, a, b); - // @ts-ignore - const xyz_d65 = XYZ_D50_to_D65(...xyz_d50); // @ts-ignore - return xyz2srgb(...xyz_d65); - } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - function Lab_to_XYZ(l, a, b) { - // Convert Lab to D50-adapted XYZ - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - const k = 24389 / 27; // 29^3/3^3 - const e = 216 / 24389; // 6^3/29^3 - const f = []; - // compute f, starting with the luminance-related term - f[1] = (l + 16) / 116; - f[0] = a / 500 + f[1]; - f[2] = f[1] - b / 200; - // compute xyz - const xyz = [ - Math.pow(f[0], 3) > e ? Math.pow(f[0], 3) : (116 * f[0] - 16) / k, - l > k * e ? Math.pow((l + 16) / 116, 3) : l / k, - Math.pow(f[2], 3) > e ? Math.pow(f[2], 3) : (116 * f[2] - 16) / k, - ]; - // Compute XYZ by scaling xyz by reference white - return xyz.map((value, i) => value * D50[i]); + if (components.length == 4) { + // @ts-ignore + t = components[3]; + // @ts-ignore + const v = getNumber(t); + // @ts-ignore + if (v < 1) { + // @ts-ignore + value += Math.round(255 * getNumber(t)) + .toString(16) + .padStart(2, "0"); + } + } + return value; } - - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - // srgb-linear -> srgb - // 0 <= r, g, b <= 1 - function srgbvalues(token) { - if (token.typ === exports.EnumToken.IdenTokenType) { - token = parseColor(token); + function hsl2hexvalues(token) { + const t = hsl2rgbvalues(token); + if (t == null) { + return null; } - switch (token.kin) { - case exports.ColorType.LIT: - case exports.ColorType.HEX: - return hex2srgbvalues(token); - case exports.ColorType.RGB: - case exports.ColorType.RGBA: - return rgb2srgb(token); - case exports.ColorType.HSL: - case exports.ColorType.HSLA: - return hsl2srgb(token); - case exports.ColorType.HWB: - return hwb2srgbvalues(token); - case exports.ColorType.LAB: - return lab2srgbvalues(token); - case exports.ColorType.LCH: - return lch2srgbvalues(token); - case exports.ColorType.OKLAB: - return oklab2srgbvalues(token); - case exports.ColorType.OKLCH: - return oklch2srgbvalues(token); - case exports.ColorType.COLOR: - return color2srgbvalues(token); + if (t.length == 4) { + t[3] = srgb2rgb(t[3]); } - return null; - } - function rgb2srgb(token) { - return (getComponents(token)?.map?.((t, index) => index == 3 - ? t.typ == exports.EnumToken.IdenTokenType && t.val == "none" - ? 1 - : getNumber(t) - : ((t.typ == exports.EnumToken.PercentageTokenType ? 255 : 1) * - getNumber(t)) / - 255) ?? null); - } - function rgb2srgbvalues(token) { - return (getComponents(token)?.map?.((t, index) => index == 3 - ? getNumber(t) - : getNumber(t) / 255) ?? null); + return `${t.reduce(toHexString, "#")}`; } - function hex2srgbvalues(token) { - const value = expandHexValue(token.kin == exports.ColorType.LIT ? COLORS_NAMES[token.val.toLowerCase()] : token.val); - const rgb = []; - for (let i = 1; i < value.length; i += 2) { - rgb.push(parseInt(value.slice(i, i + 2), 16) / 255); + function hwb2hexvalues(token) { + const t = hwb2rgbvalues(token); + if (t == null) { + return null; } - if (rgb.length == 4) { - rgb[3] = +rgb[3].toFixed(2); + if (t.length == 4) { + t[3] = srgb2rgb(t[3]); } - return rgb; - } - // xyz d65 input - function xyz2srgb(x, y, z, alpha = null) { - // @ts-ignore - return lsrgb2srgbvalues(...XYZ_to_lin_sRGB(x, y, z, alpha)); + return `${t.reduce(toHexString, "#")}`; } - function hwb2srgbvalues(token) { - const { h: hue, s: white, l: black, a: alpha } = hslvalues(token) ?? {}; - if (hue == null || white == null || black == null) { - return []; - } - const rgb = hslvalues2srgbvalues(hue, 1, 0.5); - for (let i = 0; i < 3; i++) { - rgb[i] *= 1 - white - black; - rgb[i] = rgb[i] + white; + function cmyk2hexvalues(token) { + const t = cmyk2rgbvalues(token); + if (t == null) { + return null; } - if (alpha != null && alpha != 1) { - rgb.push(alpha); + if (t.length == 4) { + t[3] = srgb2rgb(t[3]); } - return rgb; + return `#${t.reduce(toHexString, "")}`; } - function hsl2srgb(token) { - let { h, s, l, a } = hslvalues(token) ?? {}; - if (h == null || s == null || l == null) { + + function srgb2rgb(value) { + return minmax(Math.round(value * 255), 0, 255); + } + function hex2RgbToken(token) { + return rgb2RgbToken(hex2rgbvalues(token)); + } + function hsl2RgbToken(token) { + const values = hsl2rgbvalues(token); + if (values == null) { return null; } - return hslvalues2srgbvalues(h, s, l, a); + return rgb2RgbToken(values); } - function cmyk2srgbvalues(token) { - const components = getComponents(token); - if (components == null) { + function hwb2RgbToken(token) { + const values = hwb2rgbvalues(token); + if (values == null) { return null; } - // @ts-ignore - let t = components[0]; - // @ts-ignore - const c = getNumber(t); - // @ts-ignore - t = components[1]; - // @ts-ignore - const m = getNumber(t); - // @ts-ignore - t = components[2]; - // @ts-ignore - const y = getNumber(t); - // @ts-ignore - t = components[3]; - // @ts-ignore - const k = getNumber(t); - const rgb = [ - 1 - Math.min(1, c * (1 - k) + k), - 1 - Math.min(1, m * (1 - k) + k), - 1 - Math.min(1, y * (1 - k) + k), - ]; - if (components.length == 5) { - rgb.push(getNumber(components[4])); - } - return rgb; + return rgb2RgbToken(values); } - function oklab2srgbvalues(token) { - const [l, a, b, alpha] = getOKLABComponents(token) ?? []; - if (l == null || a == null || b == null) { + function cmyk2RgbToken(token) { + const values = cmyk2rgbvalues(token); + if (values == null) { return null; } - const rgb = OKLab_to_sRGB(l, a, b); - if (alpha != null && alpha != 1) { - rgb.push(alpha); - } - return rgb; + return rgb2RgbToken(values); } - function oklch2srgbvalues(token) { - const [l, c, h, alpha] = getOKLCHComponents(token) ?? []; - if (l == null || c == null || h == null) { + function oklab2RgbToken(token) { + const values = oklab2rgbvalues(token); + if (values == null) { return null; } - // @ts-ignore - const rgb = OKLab_to_sRGB(...lchvalues2labvalues(l, c, h)); - if (alpha != 1) { - rgb.push(alpha); - } - return rgb; + return rgb2RgbToken(values); } - function hslvalues(token) { - const components = getComponents(token); - if (components == null) { + function oklch2RgbToken(token) { + const values = oklch2rgbvalues(token); + if (values == null) { return null; } - let t; - // @ts-ignore - let h = getAngle(components[0]); - // @ts-ignore - t = components[1]; - // @ts-ignore - let s = getNumber(t); - // @ts-ignore - t = components[2]; - // @ts-ignore - let l = getNumber(t); - let a = null; - if (components.length == 4) { - // @ts-ignore - t = components[3]; - // @ts-ignore - a = getNumber(t); - } - return a == null ? { h, s, l } : { h, s, l, a }; - } - function hslvalues2srgbvalues(h, s, l, a = null) { - let v = l <= 0.5 ? l * (1.0 + s) : l + s - l * s; - let r = l; - let g = l; - let b = l; - if (v > 0) { - let m = l + l - v; - let sv = (v - m) / v; - h *= 6.0; - let sextant = Math.floor(h); - let fract = h - sextant; - let vsf = v * sv * fract; - let mid1 = m + vsf; - let mid2 = v - vsf; - switch (sextant) { - case 0: - r = v; - g = mid1; - b = m; - break; - case 1: - r = mid2; - g = v; - b = m; - break; - case 2: - r = m; - g = v; - b = mid1; - break; - case 3: - r = m; - g = mid2; - b = v; - break; - case 4: - r = mid1; - g = m; - b = v; - break; - case 5: - r = v; - g = m; - b = mid2; - break; - } - } - const values = [r, g, b]; - if (a != null && a != 1) { - values.push(a); - } - return values; + return rgb2RgbToken(values); } - function lab2srgbvalues(token) { - const [l, a, b, alpha] = getLABComponents(token) ?? []; - if (l == null || a == null || b == null) { + function lab2RgbToken(token) { + const values = lab2rgbvalues(token); + if (values == null) { return null; } - const rgb = Lab_to_sRGB(l, a, b); - if (alpha != null && alpha < 1) { - rgb.push(alpha); - } - return rgb; + return rgb2RgbToken(values); } - function lch2srgbvalues(token) { - const components = getLCHComponents(token); - if (components == null) { - return null; - } - // @ts-ignore - const [l, a, b, alpha] = lchvalues2labvalues(...components); - if (l == null || a == null || b == null) { + function lch2RgbToken(token) { + const values = lch2rgbvalues(token); + if (values == null) { return null; } - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const rgb = Lab_to_sRGB(l, a, b); - if (alpha != 1) { - rgb.push(alpha); - } - return rgb; + return rgb2RgbToken(values); } - // sRGB -> lRGB - function srgb2lsrgbvalues(r, g, b, a = null) { - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - const rgb = [r, g, b].map((val) => { - const abs = Math.abs(val); - if (abs <= 0.04045) { - return val / 12.92; - } - return (Math.sign(val) || 1) * Math.pow((abs + 0.055) / 1.055, 2.4); - }); - if (a != 1 && a != null) { - rgb.push(a); + function color2RgbToken(token) { + const values = color2srgbvalues(token); + if (values == null) { + return null; } - return rgb; + return rgb2RgbToken(values.map((t, index) => (index == 3 ? t : srgb2rgb(t)))); } - function lsrgb2srgbvalues(r, g, b, alpha = null) { - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - const rgb = [r, g, b].map((val) => { - let abs = Math.abs(val); - if (Math.abs(val) > 0.0031308) { - return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); - } - return 12.92 * val; - }); - if (alpha != 1 && alpha != null) { - rgb.push(alpha); + function rgb2RgbToken(values) { + const chi = [ + { typ: exports.EnumToken.NumberTokenType, val: values[0] }, + { typ: exports.EnumToken.NumberTokenType, val: values[1] }, + { typ: exports.EnumToken.NumberTokenType, val: values[2] }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: values[3] * 100 }); } - return rgb; - } - - function toHexString(acc, value) { - return acc + value.toString(16).padStart(2, "0"); + return { + typ: exports.EnumToken.ColorTokenType, + val: "rgb", + chi, + kin: exports.ColorType.RGB, + }; } - function reduceHexValue(value) { - if (value[0] != "#") { - value = COLORS_NAMES[value.toLowerCase()] ?? value; - } - const named_color = NAMES_COLORS[expandHexValue(value)]; - if (value.length == 7) { - if (value[1] == value[2] && value[3] == value[4] && value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } + function hex2rgbvalues(token) { + const value = expandHexValue(token.kin == exports.ColorType.LIT ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb = []; + for (let i = 1; i < value.length; i += 2) { + rgb.push(parseInt(value.slice(i, i + 2), 16)); } - else if (value.length == 9) { - if (value[1] == value[2] && value[3] == value[4] && value[5] == value[6] && value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7] == "f" ? "" : value[7]}`; + if (rgb.length == 4) { + if (rgb[3] == 255) { + rgb.pop(); } - if (value.endsWith("ff")) { - value = value.slice(0, -2); + else { + rgb[3] = +(rgb[3] / 255).toFixed(2); } } - return named_color != null && named_color.length <= value.length ? named_color : value; + return rgb; } - function expandHexValue(value) { - if (value.length == 4) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; - } - if (value.length == 5) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; - } - return value; + function hwb2rgbvalues(token) { + return hwb2srgbvalues(token)?.map?.((t, index) => (index == 3 ? t : srgb2rgb(t))) ?? null; } - function rgb2HexToken(token) { - let value = rgb2hexvalues(token); - if (value == null) { - return null; - } - return hexToken(value); - } - function hsl2HexToken(token) { - let value = hsl2hexvalues(token); - if (value == null) { - return null; - } - return hexToken(value); - } - function cmyk2HexToken(token) { - let value = cmyk2hexvalues(token); - if (value == null) { - return null; - } - return hexToken(value); - } - function hwb2HexToken(token) { - let value = hwb2hexvalues(token); - if (value == null) { - return null; - } - return hexToken(value); - } - function color2HexToken(token) { - let value = color2srgbvalues(token); - if (value == null) { - return null; - } - return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); - } - function oklab2HexToken(token) { - let value = oklab2srgbvalues(token); - if (value == null) { - return null; - } - return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); - } - function oklch2HexToken(token) { - let value = oklch2srgbvalues(token); - if (value == null) { - return null; - } - return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); - } - function lab2HexToken(token) { - let value = lab2srgbvalues(token); - if (value == null) { - return null; - } - return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); - } - function lch2HexToken(token) { - let value = lch2srgbvalues(token); - if (value == null) { - return null; - } - return hexToken(value.reduce((acc, curr) => acc + srgb2rgb(curr).toString(16).padStart(2, "0"), "#")); - } - function hexToken(value) { - value = reduceHexValue(value); - return { - typ: exports.EnumToken.ColorTokenType, - val: value, - kin: value[0] == "#" ? exports.ColorType.HEX : exports.ColorType.LIT, - }; - } - function rgb2hexvalues(token) { - let value = "#"; - let t; - // @ts-ignore - const components = getComponents(token); - if (components == null || components.length < 3) { - return null; - } - // @ts-ignore - for (let i = 0; i < 3; i++) { - // @ts-ignore - t = components[i]; - value += - ( // @ts-expect-error - t.typ == exports.EnumToken.Iden && t.val == "none" - ? "0" - : Math.round(getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 255 : 1))) - .toString(16) - .padStart(2, "0"); - } - // @ts-ignore - if (components.length == 4) { - // @ts-ignore - t = components[3]; - // @ts-ignore - const v = getNumber(t); - // @ts-ignore - if (v < 1) { - // @ts-ignore - value += Math.round(255 * getNumber(t)) - .toString(16) - .padStart(2, "0"); - } - } - return value; - } - function hsl2hexvalues(token) { - const t = hsl2rgbvalues(token); - if (t == null) { - return null; - } - if (t.length == 4) { - t[3] = srgb2rgb(t[3]); - } - return `${t.reduce(toHexString, "#")}`; - } - function hwb2hexvalues(token) { - const t = hwb2rgbvalues(token); - if (t == null) { - return null; - } - if (t.length == 4) { - t[3] = srgb2rgb(t[3]); - } - return `${t.reduce(toHexString, "#")}`; - } - function cmyk2hexvalues(token) { - const t = cmyk2rgbvalues(token); - if (t == null) { - return null; - } - if (t.length == 4) { - t[3] = srgb2rgb(t[3]); - } - return `#${t.reduce(toHexString, "")}`; - } - - function srgb2rgb(value) { - return minmax(Math.round(value * 255), 0, 255); - } - function hex2RgbToken(token) { - return rgb2RgbToken(hex2rgbvalues(token)); - } - function hsl2RgbToken(token) { - const values = hsl2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function hwb2RgbToken(token) { - const values = hwb2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function cmyk2RgbToken(token) { - const values = cmyk2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function oklab2RgbToken(token) { - const values = oklab2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function oklch2RgbToken(token) { - const values = oklch2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function lab2RgbToken(token) { - const values = lab2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function lch2RgbToken(token) { - const values = lch2rgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values); - } - function color2RgbToken(token) { - const values = color2srgbvalues(token); - if (values == null) { - return null; - } - return rgb2RgbToken(values.map((t, index) => (index == 3 ? t : srgb2rgb(t)))); - } - function rgb2RgbToken(values) { - const chi = [ - { typ: exports.EnumToken.NumberTokenType, val: values[0] }, - { typ: exports.EnumToken.NumberTokenType, val: values[1] }, - { typ: exports.EnumToken.NumberTokenType, val: values[2] }, - ]; - if (values.length == 4) { - chi.push({ typ: exports.EnumToken.PercentageTokenType, val: values[3] * 100 }); - } - return { - typ: exports.EnumToken.ColorTokenType, - val: "rgb", - chi, - kin: exports.ColorType.RGB, - }; - } - function hex2rgbvalues(token) { - const value = expandHexValue(token.kin == exports.ColorType.LIT ? COLORS_NAMES[token.val.toLowerCase()] : token.val); - const rgb = []; - for (let i = 1; i < value.length; i += 2) { - rgb.push(parseInt(value.slice(i, i + 2), 16)); - } - if (rgb.length == 4) { - if (rgb[3] == 255) { - rgb.pop(); - } - else { - rgb[3] = +(rgb[3] / 255).toFixed(2); - } - } - return rgb; - } - function hwb2rgbvalues(token) { - return hwb2srgbvalues(token)?.map?.((t, index) => (index == 3 ? t : srgb2rgb(t))) ?? null; - } - function hsl2rgbvalues(token) { - let { h, s, l, a } = hslvalues(token) ?? {}; - if (h == null || s == null || l == null) { + function hsl2rgbvalues(token) { + let { h, s, l, a } = hslvalues(token) ?? {}; + if (h == null || s == null || l == null) { return null; } return hslvalues2srgbvalues(h, s, l) @@ -9696,7 +8864,7 @@ if (values.length == 4 && values[3] != 1) { chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { typ: exports.EnumToken.PercentageTokenType, - val: values[3] * 100, + val: toPrecisionValue(values[3], 2) * 100, }); } return { @@ -9707,7 +8875,7 @@ }; } function rgb2hslvalues(token) { - const chi = getComponents(token); + const chi = getColorComponents(token); if (chi == null || chi.length < 3) { return null; } @@ -9881,14 +9049,14 @@ function hwbToken(values) { values[0] = toPrecisionAngle(values[0] * 360); const chi = [ - { typ: exports.EnumToken.NumberTokenType, val: values[0] }, - { typ: exports.EnumToken.PercentageTokenType, val: toPrecisionValue(values[1]) * 100 }, - { typ: exports.EnumToken.PercentageTokenType, val: toPrecisionValue(values[2]) * 100 }, + { typ: exports.EnumToken.NumberTokenType, val: toPrecisionAngle(values[0]) }, + { typ: exports.EnumToken.PercentageTokenType, val: toPrecisionValue(values[1] * 100) }, + { typ: exports.EnumToken.PercentageTokenType, val: toPrecisionValue(values[2] * 100) }, ]; if (values.length == 4) { chi.push({ typ: exports.EnumToken.LiteralTokenType, val: "/" }, { typ: exports.EnumToken.PercentageTokenType, - val: values[3] * 100, + val: toPrecisionValue(values[3], 2) * 100 }); } return { @@ -9900,7 +9068,7 @@ } function rgb2hwbvalues(token) { // @ts-ignore - return srgb2hwb(...getComponents(token).map((t, index) => { + return srgb2hwb(...getColorComponents(token).map((t, index) => { if (index == 3) { return getNumber(t); } @@ -9913,7 +9081,7 @@ } function hsl2hwbvalues(token) { // @ts-ignore - return hslvalues2hwbvalues(...getComponents(token).map((t, index) => { + return hslvalues2hwbvalues(...getColorComponents(token).map((t, index) => { if (index == 3 && t.typ == exports.EnumToken.IdenTokenType && t.val == "none") { return 1; } @@ -10170,7 +9338,7 @@ } function interpolateHue(interpolationMethod, h1, h2) { - switch (interpolationMethod.val) { + switch (interpolationMethod) { case "longer": if (h2 - h1 < 180 && h2 - h1 > 0) { h1 += 360; @@ -10202,227 +9370,283 @@ } return [h1, h2]; } - function colorMix(colorSpace, hueInterpolationMethod, color1, percentage1, color2, percentage2) { - if (color1.val.toLowerCase() == "currentcolor" || color2.val == "currentcolor".toLowerCase()) { - return null; - } - if (hueInterpolationMethod != null && isRectangularOrthogonalColorspace(colorSpace)) { + /** + * Compute color mix + * @param args + * @returns + */ + function colorMix(...args) { + // invalid color or custom color profile + if (args.length == 0 || args[0].typ === exports.EnumToken.DashedIdenTokenType) { return null; } - if (isPolarColorspace(colorSpace) && hueInterpolationMethod == null) { - hueInterpolationMethod = { typ: exports.EnumToken.IdenTokenType, val: "shorter" }; - } - if (percentage1 == null) { - if (percentage2 == null) { - // @ts-ignore - percentage1 = { typ: exports.EnumToken.NumberTokenType, val: 0.5 }; - // @ts-ignore - percentage2 = { typ: exports.EnumToken.NumberTokenType, val: 0.5 }; - } - else { - if (+percentage2.val <= 0) { - return null; - } - if (+percentage2.val >= 100) { - percentage2 = { typ: exports.EnumToken.NumberTokenType, val: 1 }; - } - // @ts-ignore - percentage1 = { typ: exports.EnumToken.NumberTokenType, val: 1 - percentage2.val / 100 }; + let i = 0; + let missingPercentageCount = 0; + let totalPercentage = 0; + let leftOverPercentage = 0; + let colorSpace = "oklab"; + let hueInterpolationMethod = "shorter"; + let values = null; + const colors = []; + const percentages = []; + const srgbComponentValues = []; + const colorComponents = []; + if (args[i]?.typ === exports.EnumToken.IdenTokenType) { + if (!equalsIgnoreCase(args[i++].val, "in")) { + return null; } - } - else { - // @ts-ignore - if (percentage1.val <= 0) { + if (args[i]?.typ !== exports.EnumToken.IdenTokenType) { return null; } - if (percentage2 == null) { - // @ts-ignore - if (percentage1.val >= 100) { - // @ts-ignore - percentage1 = { typ: exports.EnumToken.NumberTokenType, val: 1 }; - } - // @ts-ignore - percentage2 = { typ: exports.EnumToken.NumberTokenType, val: 1 - percentage1.val / 100 }; + if (isRectangularOrthogonalColorspace(args[i])) { + colorSpace = args[i++].val; } - else { - // @ts-ignore - if (percentage2.val <= 0) { + else if (isPolarColorspace(args[i])) { + colorSpace = args[i++].val; + if (args[i]?.typ !== exports.EnumToken.IdenTokenType && args[i]?.typ !== exports.EnumToken.ColorTokenType) { return null; } + if (args[i].typ === exports.EnumToken.IdenTokenType && !equalsIgnoreCase(args[i].val, "hue")) { + if (args[i]?.typ !== exports.EnumToken.IdenTokenType) { + return null; + } + hueInterpolationMethod = args[i++].val; + switch (hueInterpolationMethod) { + case "increasing": + case "decreasing": + case "longer": + case "shorter": + break; + default: + return null; + } + if (!equalsIgnoreCase(args[i++].val, "hue")) { + return null; + } + } } } - let values1 = srgbvalues(color1); - let values2 = srgbvalues(color2); - if (values1 == null || values2 == null) { - return null; - } - const components1 = getComponents(color1); - const components2 = getComponents(color2); - if (components1 == null || components2 == null) { - return null; - } - if (components1[3] != null && - components1[3].typ == exports.EnumToken.IdenTokenType && - components1[3].val == "none" && - values2.length == 4) { - values1[3] = values2[3]; - } - if (components2[3] != null && - components2[3].typ == exports.EnumToken.IdenTokenType && - components2[3].val == "none" && - values1.length == 4) { - values2[3] = values1[3]; - } - const p1 = getNumber(percentage1); - const p2 = getNumber(percentage2); - const mul1 = values1.length == 4 ? values1.pop() : 1; - const mul2 = values2.length == 4 ? values2.pop() : 1; - const mul = mul1 * p1 + mul2 * p2; - const calculate = () => [colorSpace].concat( - // @ts-ignore - values1 - .map((v1, i) => { - return { - typ: exports.EnumToken.NumberTokenType, - val: (mul1 * v1 * p1 + mul2 * values2[i] * p2) / mul, - }; - }) - .concat(mul == 1 - ? [] - : [ - { - typ: exports.EnumToken.NumberTokenType, - val: mul, - }, - ])); - switch (colorSpace.val) { - case "srgb": - break; - case "display-p3": - // @ts-ignore - values1 = srgb2p3values(...values1); - // @ts-ignore - values2 = srgb2p3values(...values2); - break; - case "a98-rgb": - // @ts-ignore - values1 = srgb2a98values(...values1); - // @ts-ignore - values2 = srgb2a98values(...values2); - break; - case "prophoto-rgb": - // @ts-ignore - values1 = srgb2prophotorgbvalues(...values1); - // @ts-ignore - values2 = srgb2prophotorgbvalues(...values2); - break; - case "srgb-linear": - // @ts-ignore - values1 = srgb2lsrgbvalues(...values1); - // @ts-ignore - values2 = srgb2lsrgbvalues(...values2); - break; - case "rec2020": - // @ts-ignore - values1 = srgb2rec2020values(...values1); - // @ts-ignore - values2 = srgb2rec2020values(...values2); - break; - case "xyz": - case "xyz-d65": - case "xyz-d50": - // @ts-ignore - values1 = srgb2xyz_d50(...values1); - // @ts-ignore - values2 = srgb2xyz_d50(...values2); - if (colorSpace.val == "xyz-d50") { + while (i < args.length) { + if (args[i].typ !== exports.EnumToken.ColorTokenType || + exports.ColorType.SYS == args[i].kin || + exports.ColorType.DPSYS == args[i].kin || + exports.ColorType.NON_STD == args[i].kin || + exports.ColorType.CUSTOM_COLOR == args[i].kin || + equalsIgnoreCase(args[i].val, "currentcolor")) { + return null; + } + colorComponents.push(getColorComponents(args[i])); + values = srgbvalues(args[i]); + if (values == null) { + return null; + } + switch (colorSpace) { + case "srgb": + break; + case "display-p3": // @ts-ignore - values1 = XYZ_D65_to_D50(...values1); + values = srgb2p3values(...values); + break; + case "a98-rgb": // @ts-ignore - values2 = XYZ_D65_to_D50(...values2); - } - break; - case "rgb": - // @ts-ignore - values1 = srgb2rgb(...values1); - // @ts-ignore - values2 = srgb2rgb(...values2); - break; - case "hsl": - // @ts-ignore - values1 = srgb2hslvalues(...values1); - // @ts-ignore - values2 = srgb2hslvalues(...values2); - break; - case "hwb": - // @ts-ignore - values1 = srgb2hwb(...values1); - // @ts-ignore - values2 = srgb2hwb(...values2); - break; - case "lab": - // @ts-ignore - values1 = srgb2labvalues(...values1); - // @ts-ignore - values2 = srgb2labvalues(...values2); - break; - case "lch": - // @ts-ignore - values1 = srgb2lch(...values1); - // @ts-ignore - values2 = srgb2lch(...values2); - break; - case "oklab": - // @ts-ignore - values1 = srgb2oklab(...values1); - // @ts-ignore - values2 = srgb2oklab(...values2); - break; - case "oklch": - // @ts-ignore - values1 = srgb2oklch(...values1); - // @ts-ignore - values2 = srgb2oklch(...values2); + values = srgb2a98values(...values); + break; + case "prophoto-rgb": + // @ts-ignore + values = srgb2prophotorgbvalues(...values); + break; + case "srgb-linear": + // @ts-ignore + values = srgb2lsrgbvalues(...values); + break; + case "rec2020": + // @ts-ignore + values = srgb2rec2020values(...values); + break; + case "xyz": + case "xyz-d65": + // @ts-ignore + values = srgb2xyz_d65(...values); + break; + case "xyz-d50": + // @ts-ignore + values = XYZ_D65_to_D50(...srgb2xyz_d65(...values)); + break; + case "rgb": + // @ts-ignore + values = srgb2rgb(...values); + break; + case "hsl": + // @ts-ignore + values = srgb2hslvalues(...values); + break; + case "hwb": + // @ts-ignore + values = srgb2hwb(...values); + break; + case "lab": + // @ts-ignore + values = srgb2labvalues(...values); + break; + case "lch": + // @ts-ignore + values = srgb2lch(...values); + break; + case "oklab": + // @ts-ignore + values = srgb2oklab(...values); + break; + case "oklch": + // @ts-ignore + values = srgb2oklch(...values); + break; + default: + return null; + } + srgbComponentValues.push(values); + colors.push(args[i++]); + if (i >= args.length) { + missingPercentageCount++; + percentages.push(null); break; - default: - return null; + } + if (args[i]?.typ === exports.EnumToken.ColorTokenType) { + percentages.push(null); + missingPercentageCount++; + continue; + } + if (args[i]?.typ === exports.EnumToken.PercentageTokenType || + (args[i]?.typ === exports.EnumToken.NumberTokenType && 0 == args[i].val)) { + if (args[i].val < 0) { + return null; + } + percentages.push(minmax(getNumber(args[i++]), 0, 1)); + totalPercentage += percentages.at(-1); + } + } + // normalize percentages + if (missingPercentageCount > 0) { + let normalizedTotalPercentages = totalPercentage > 1 ? 0 : 1 - totalPercentage; + for (i = 0; i < percentages.length; i++) { + if (percentages[i] == null) { + percentages[i] = normalizedTotalPercentages / missingPercentageCount; + } + } + totalPercentage = 0; + for (i = 0; i < percentages.length; i++) { + totalPercentage += percentages[i]; + } } + if (totalPercentage != 1) { + if (totalPercentage < 1) { + leftOverPercentage = 1 - totalPercentage; + } + const perc = totalPercentage == 0 ? 1 : totalPercentage; + // scale down percentages + for (i = 0; i < percentages.length; i++) { + percentages[i] = percentages[i] / perc; + } + totalPercentage = 1; + } + i = colors.length; + let currentIndex = 0; + let r1; + let r2; + let r; + let mult1; + let mult2; + let mult; + let premult1; + let premult2; + let mixedPremult; + let colorSpace1; + const stack = []; const lchSpaces = ["lch", "oklch"]; - const colorSpace1 = exports.ColorType[color1.kin].toLowerCase().replaceAll("_", "-"); - const colorSpace2 = exports.ColorType[color2.kin].toLowerCase().replaceAll("_", "-"); - // powerless - if (lchSpaces.includes(colorSpace1) || lchSpaces.includes(colorSpace.val)) { - if ((components1[2].typ == exports.EnumToken.IdenTokenType && components1[2].val == "none") || - values1[2] == 0) { - values1[2] = values2[2]; - } - } - // powerless - if (lchSpaces.includes(colorSpace2) || lchSpaces.includes(colorSpace.val)) { - if ((components2[2].typ == exports.EnumToken.IdenTokenType && components2[2].val == "none") || - values2[2] == 0) { - values2[2] = values1[2]; - } - } - if (hueInterpolationMethod != null) { - let hueIndex = 2; - let multiplier = 1; - if (["hwb", "hsl"].includes(colorSpace.val)) { - hueIndex = 0; - multiplier = 360; - } - const [h1, h2] = interpolateHue(hueInterpolationMethod, values1[hueIndex] * multiplier, values2[hueIndex] * multiplier); - values1[hueIndex] = h1 / multiplier; - values2[hueIndex] = h2 / multiplier; - } - switch (colorSpace.val) { + i = srgbComponentValues.length; + while (i--) { + stack.push({ + color: srgbComponentValues[i], + alpha: percentages[i], + }); + } + // @ts-expect-error + colorSpace1 = exports.ColorType[colorComponents.at(-1).kin]?.toLowerCase?.(); + if (colorComponents[0][3] != null && + colorComponents[0][3].typ == exports.EnumToken.IdenTokenType && + colorComponents[0][3].val == "none" && + colorComponents[1].length == 4) { + // fix powerless alpha for last color if previous color has alpha + stack[stack.length - 1].color[3] = stack[stack.length - 2].color[3]; + } + // powerless hue for lch spaces + if (lchSpaces.includes(colorSpace1) || lchSpaces.includes(colorSpace)) { + if ((stack.length > 1 && + colorComponents[0][2].typ == exports.EnumToken.IdenTokenType && + colorComponents[0][2].val == "none") || + stack[stack.length - 1].color[2] == 0) { + stack[stack.length - 1].color[2] = stack[stack.length - 2].color[2]; + } + } + while (stack.length > 1) { + r1 = stack.pop(); + r2 = stack.pop(); + // r2 powerless alpha + if (colorComponents[++currentIndex][3] != null && + colorComponents[currentIndex][3].typ == exports.EnumToken.IdenTokenType && + colorComponents[currentIndex][3].val == "none") { + // fix powerless alpha for last color if previous color has alpha + r2.color[3] = r1.color[3]; + } + // @ts-expect-error + colorSpace1 = exports.ColorType[colorComponents[currentIndex].kin]?.toLowerCase?.(); + // powerless hue for lch spaces + if (lchSpaces.includes(colorSpace1) || lchSpaces.includes(colorSpace)) { + if ((colorComponents[currentIndex][2].typ == exports.EnumToken.IdenTokenType && + colorComponents[currentIndex][2].val == "none") || + r2.color[2] == 0) { + r2.color[2] = r1.color[2]; + } + } + if (hueInterpolationMethod != null) { + let hueIndex = 2; + let multiplier = 1; + if (colorSpace == "hwb" || colorSpace == "hsl") { + hueIndex = 0; + multiplier = 360; + } + const [h1, h2] = interpolateHue(hueInterpolationMethod, r1.color[hueIndex] * multiplier, r2.color[hueIndex] * multiplier); + r1.color[hueIndex] = h1 / multiplier; + r2.color[hueIndex] = h2 / multiplier; + } + mult1 = r1.color[3] ?? 1; + mult2 = r2.color[3] ?? 1; + mult = mult1 * r1.alpha + mult2 * r2.alpha; + premult1 = [r1.color[0] * mult1 * r1.alpha, r1.color[1] * mult1 * r1.alpha, r1.color[2] * mult1 * r1.alpha]; + premult2 = [r2.color[0] * mult2 * r2.alpha, r2.color[1] * mult2 * r2.alpha, r2.color[2] * mult2 * r2.alpha]; + mixedPremult = [premult1[0] + premult2[0], premult1[1] + premult2[1], premult1[2] + premult2[2]]; + if (mult == 0) { + r = { color: [0, 0, 0, 0], alpha: 0 }; + } + else { + r = { + color: [mixedPremult[0] / mult, mixedPremult[1] / mult, mixedPremult[2] / mult], + alpha: mult, + }; + } + stack.push(r); + } + const result = stack.pop(); + values = result.color; + values.length = 3; + const alpha = result.alpha * (1 - leftOverPercentage); + if (alpha != 1) { + values.push(alpha); + } + switch (colorSpace) { case "xyz": case "xyz-d65": case "xyz-d50": - let values = values1 - .map((v1, i) => (mul1 * v1 * p1 + mul2 * values2[i] * p2) / mul) - .concat(mul == 1 ? [] : [mul]); - if (colorSpace.val == "xyz-d50") { + if (colorSpace == "xyz-d50") { // @ts-ignore values = xyzd502lch(...values); } @@ -10437,7 +9661,7 @@ chi: values.map((v) => { return { typ: exports.EnumToken.NumberTokenType, - val: v, + val: toPrecisionValue(v), }; }), kin: exports.ColorType.LCH, @@ -10450,7 +9674,14 @@ return { typ: exports.EnumToken.ColorTokenType, val: "color", - chi: calculate(), + chi: [{ typ: exports.EnumToken.IdenTokenType, val: colorSpace }].concat( + // @ts-expect-error + values.map((v) => { + return { + typ: exports.EnumToken.NumberTokenType, + val: toPrecisionValue(v), + }; + })), kin: exports.ColorType.COLOR, cal: "col", }; @@ -10461,44 +9692,43 @@ case "lch": case "oklab": case "oklch": - if (["hsl", "hwb"].includes(colorSpace.val)) { - // @ts-ignore - if (values1[2] < 0) { - // @ts-ignore - values1[2] += 1; - } + if (colorSpace == "hsl" || colorSpace == "hwb") { // @ts-ignore - if (values2[2] < 0) { + if (values[2] < 0) { // @ts-ignore - values2[2] += 1; + values[2] += 1; } } - else if (["lch", "oklch"].includes(colorSpace.val)) { + else if (colorSpace == "lch" || colorSpace == "oklch") { // @ts-ignore - if (values1[2] < 0) { + if (values[2] < 0) { // @ts-ignore - values1[2] += 360; + values[2] += 360; } - // @ts-ignore - if (values2[2] < 0) { + else if (values[2] > 360) { // @ts-ignore - values2[2] += 360; + values[2] %= 360; } } // @ts-ignore const result = { typ: exports.EnumToken.ColorTokenType, - val: colorSpace.val, - chi: calculate().slice(1), - kin: exports.ColorType[colorSpace.val.toUpperCase().replaceAll("-", "_")], + val: colorSpace, + chi: values.map((v) => { + return { + typ: exports.EnumToken.NumberTokenType, + val: toPrecisionValue(v), + }; + }), + kin: exports.ColorType[colorSpace.toUpperCase().replaceAll("-", "_")], }; - if (colorSpace.val == "hsl" || colorSpace.val == "hwb") { + if (colorSpace == "hsl" || colorSpace == "hwb") { // @ts-ignore - result.chi[0] = { typ: exports.EnumToken.AngleTokenType, val: result.chi[0].val * 360 }; + result.chi[0] = { typ: exports.EnumToken.AngleTokenType, val: toPrecisionAngle(result.chi[0].val * 360) }; // @ts-ignore - result.chi[1] = { typ: exports.EnumToken.PercentageTokenType, val: result.chi[1].val * 100 }; + result.chi[1] = { typ: exports.EnumToken.PercentageTokenType, val: toPrecisionValue(result.chi[1].val) * 100 }; // @ts-ignore - result.chi[2] = { typ: exports.EnumToken.PercentageTokenType, val: result.chi[2].val * 100 }; + result.chi[2] = { typ: exports.EnumToken.PercentageTokenType, val: toPrecisionValue(result.chi[2].val) * 100 }; } return result; } @@ -10744,7 +9974,7 @@ return defaultReturn; } } - let typ = l.typ == exports.EnumToken.NumberTokenType + let typ = l.typ == exports.EnumToken.NumberTokenType || l.typ === exports.EnumToken.IdenTokenType ? r.typ : r.typ == exports.EnumToken.NumberTokenType ? l.typ @@ -10753,9 +9983,10 @@ : l.typ; // @ts-expect-error let v1 = l.val?.typ == exports.EnumToken.FractionTokenType ? l.val : getValue$1(l); - let v2 = - // @ts-expect-error - r.val?.typ == exports.EnumToken.FractionTokenType ? r.val : getValue$1(r); + let v2 = r.val?.typ == exports.EnumToken.FractionTokenType + ? // @ts-expect-error + r.val + : getValue$1(r); if (op == exports.EnumToken.Mul) { if (l.typ != exports.EnumToken.NumberTokenType && r.typ != exports.EnumToken.NumberTokenType) { if (typeof v1 == "number" && l.typ == exports.EnumToken.PercentageTokenType) { @@ -10777,7 +10008,7 @@ // @ts-ignore const val = compute$1(v1, v2, op); const token = { - ...(l.typ == exports.EnumToken.NumberTokenType ? r : l), + ...(l.typ === exports.EnumToken.NumberTokenType || l.typ === exports.EnumToken.IdenTokenType ? r : l), typ, val /* : typeof val == 'number' ? minifyNumber(val) : val */, }; @@ -10812,8 +10043,8 @@ // @ts-ignore let val = value[0].typ == exports.EnumToken.NumberTokenType ? +value[0].val - // @ts-expect-error - : value[0].l.val / value[0].r.val; + : // @ts-expect-error + value[0].l.val / value[0].r.val; return [ { typ: exports.EnumToken.NumberTokenType, @@ -11061,12 +10292,15 @@ } isOp = opList.includes(tokens[i].typ === exports.EnumToken.Plus ? exports.EnumToken.Add : tokens[i].typ); if (isOp || + tokens[i].typ === exports.EnumToken.Star || // @ts-ignore (tokens[i].typ == exports.EnumToken.LiteralTokenType && ops.includes(tokens[i].val))) { tokens.splice(i - 1, 3, { typ: exports.EnumToken.BinaryExpressionTokenType, op: isOp - ? (tokens[i].typ === exports.EnumToken.Plus ? exports.EnumToken.Add : tokens[i].typ) + ? tokens[i].typ === exports.EnumToken.Plus + ? exports.EnumToken.Add + : tokens[i].typ : getArithmeticOperation(tokens[i].val), l: factorToken(tokens[i - 1]), r: factorToken(tokens[i + 1]), @@ -11077,7 +10311,17 @@ return tokens; } - function parseRelativeColor(relativeKeys, original, rExp, gExp, bExp, aExp) { + /** + * Parse relative color components + * @param relativeKeys + * @param original + * @param rExp + * @param gExp + * @param bExp + * @param aExp + * @returns + */ + function parseRelativeColorComponents(relativeKeys, original, rExp, gExp, bExp, aExp) { let r; let g; let b; @@ -11087,9 +10331,68 @@ // colorFuncColorSpace x,y,z or r,g,b const names = relativeKeys.startsWith("xyz") ? "xyz" - : ["srgb", "srgb-linear", "display-p3", "a98-rgb", "prophoto-rgb", "rec2020", "rgb"].includes(relativeKeys.toLowerCase()) + : ["srgb", "srgb-linear", "display-p3", "a98-rgb", "prophoto-rgb", "rec2020", "rgb"].some((t) => equalsIgnoreCase(t, relativeKeys)) ? "rgb" : relativeKeys.slice(-3); + const allComponents = [rExp, gExp, bExp, aExp]; + const components = getColorComponents(original); + const validKeys = names.split(""); + let val = ""; + if (components != null) { + allComponents.push(...components); + } + // ensure all components are valid for the color space + for (const component of allComponents) { + if (component == null) { + continue; + } + if (component.typ == exports.EnumToken.IdenTokenType) { + val = component.val.toLowerCase(); + if ( + // @ts-expect-error + typeof Math[val.toUpperCase()] !== "number" && + val != "in" && + val != "hue" && + val != "from" && + val != "alpha" && + val != "none" && + val != "shorter" && + val != "longer" && + val != "increasing" && + val != "decreasing" && + !colorsFunc.includes(val) && + !colorFuncColorSpace.includes(val) && + !validKeys.includes(val)) { + return null; + } + continue; + } + if (component.typ === exports.EnumToken.MathFunctionTokenType && + equalsIgnoreCase("calc", component.val)) { + for (const { value } of walkValues(component.chi)) { + if (value.typ == exports.EnumToken.IdenTokenType) { + val = value.val.toLowerCase(); + if ( + // @ts-expect-error + typeof Math[val.toUpperCase()] !== "number" && + val != "in" && + val != "hue" && + val != "from" && + val != "alpha" && + val != "none" && + val != "shorter" && + val != "longer" && + val != "increasing" && + val != "decreasing" && + !colorsFunc.includes(val) && + !colorFuncColorSpace.includes(val) && + !validKeys.includes(val)) { + return null; + } + } + } + } + } const converted = (convertColor(original, exports.ColorType[relativeKeys.toUpperCase().replaceAll("-", "_")])); if (converted == null) { return null; @@ -11135,21 +10438,29 @@ } : aExp), }; - const result = computeComponentValue(keys, converted, values); + const result = computeComponentValue(keys, values); if (result?.alpha?.typ == exports.EnumToken.NumberTokenType && result.alpha.val === 1) { const { alpha, ...components } = result; return components; } return result; - // return computeComponentValue(keys, converted, values); } + /** + * Get token numeric value + * @param t + * @param converted + * @param component + * @returns + */ function getValue(t, converted, component) { if (t.typ == exports.EnumToken.PercentageTokenType) { let value = getNumber(t); - let colorSpace = exports.ColorType[converted.kin].toLowerCase().replaceAll("-", "_"); - if (colorSpace in colorRange) { - // @ts-ignore - value *= colorRange[colorSpace][component].at(-1); + if (converted != null) { + let colorSpace = exports.ColorType[converted.kin].toLowerCase().replaceAll("-", "_"); + if (colorSpace in colorRange) { + // @ts-ignore + value *= colorRange[colorSpace][component].at(-1); + } } return { typ: exports.EnumToken.NumberTokenType, @@ -11158,7 +10469,13 @@ } return t; } - function computeComponentValue(expr, converted, values) { + /** + * Compute component value + * @param expr + * @param values + * @returns + */ + function computeComponentValue(expr, values) { for (const object of [values, expr]) { if ("h" in object) { // normalize hue @@ -11313,7 +10630,7 @@ { typ: exports.EnumToken.PercentageTokenType, // @ts-ignore - val: toPrecisionValue(curr) * 100, + val: toPrecisionValue(curr * 100), }, ] : [ @@ -11324,7 +10641,7 @@ }, { typ: exports.EnumToken.PercentageTokenType, - val: toPrecisionValue(curr) * 100, + val: toPrecisionValue(curr, 2) * 100, }, ], []), kin: exports.ColorType.DEVICE_CMYK, @@ -11389,9648 +10706,10515 @@ return multiplyMatrices(M, [x, y, z]).concat(a == null || a == 1 ? [] : [a]); } - const epsilon = 1e-5; - function identity() { - return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; - } - function normalize$1(point) { - const [x, y, z] = point; - const norm = Math.sqrt(point[0] * point[0] + point[1] * point[1] + point[2] * point[2]); - return norm === 0 ? [0, 0, 0] : [x / norm, y / norm, z / norm]; - } - function dot(point1, point2) { - if (point1.length === 4 && point2.length === 4) { - return point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2] + point1[3] * point2[3]; - } - return point1[0] * point2[0] + point1[1] * point2[1] + point1[2] * point2[2]; - } - function multiply(matrixA, matrixB) { - let result = new Array(16).fill(0); - for (let i = 0; i < 4; i++) { - for (let j = 0; j < 4; j++) { - for (let k = 0; k < 4; k++) { - // Utiliser l'indexation linéaire pour accéder aux éléments - // Pour une matrice 4x4, l'index est (row * 4 + col) - result[j * 4 + i] += matrixA[k * 4 + i] * matrixB[j * 4 + k]; - } - } - } - return result; - } - function inverse(matrix) { - // Create augmented matrix [matrix | identity] - let augmented = [ - ...matrix.slice(0, 4), - 1, 0, 0, 0, - ...matrix.slice(4, 8), - 0, 1, 0, 0, - ...matrix.slice(8, 12), - 0, 0, 1, 0, - ...matrix.slice(12, 16), - 0, 0, 0, 1 - ]; - // Gaussian elimination with partial pivoting - for (let col = 0; col < 4; col++) { - // Find pivot row with maximum absolute value - let maxRow = col; - let maxVal = Math.abs(augmented[col * 4 + col]); - for (let row = col + 1; row < 4; row++) { - let val = Math.abs(augmented[row * 4 + col]); - if (val > maxVal) { - maxVal = val; - maxRow = row; - } - } - // Check for singularity - if (maxVal < 1e-5) { - return null; - } - // Swap rows if necessary - if (maxRow !== col) { - [augmented[col], augmented[maxRow]] = [augmented[maxRow], augmented[col]]; - } - // Scale pivot row to make pivot element 1 - let pivot = augmented[col * 4 + col]; - for (let j = 0; j < 8; j++) { - augmented[col * 4 + j] /= pivot; - } - // Eliminate column in other rows - for (let row = 0; row < 4; row++) { - if (row !== col) { - let factor = augmented[row * 4 + col]; - for (let j = 0; j < 8; j++) { - augmented[row * 4 + j] -= factor * augmented[col * 4 + j]; - } - } - } + var ValidationTokenEnum; + (function (ValidationTokenEnum) { + ValidationTokenEnum[ValidationTokenEnum["Root"] = 0] = "Root"; + ValidationTokenEnum[ValidationTokenEnum["Keyword"] = 1] = "Keyword"; + ValidationTokenEnum[ValidationTokenEnum["PropertyType"] = 2] = "PropertyType"; + ValidationTokenEnum[ValidationTokenEnum["DeclarationType"] = 3] = "DeclarationType"; + ValidationTokenEnum[ValidationTokenEnum["AtRule"] = 4] = "AtRule"; + ValidationTokenEnum[ValidationTokenEnum["FunctionDefinition"] = 5] = "FunctionDefinition"; + ValidationTokenEnum[ValidationTokenEnum["OpenBracket"] = 6] = "OpenBracket"; + ValidationTokenEnum[ValidationTokenEnum["CloseBracket"] = 7] = "CloseBracket"; + ValidationTokenEnum[ValidationTokenEnum["OpenParenthesis"] = 8] = "OpenParenthesis"; + ValidationTokenEnum[ValidationTokenEnum["CloseParenthesis"] = 9] = "CloseParenthesis"; + ValidationTokenEnum[ValidationTokenEnum["Comma"] = 10] = "Comma"; + ValidationTokenEnum[ValidationTokenEnum["Pipe"] = 11] = "Pipe"; + ValidationTokenEnum[ValidationTokenEnum["Column"] = 12] = "Column"; + ValidationTokenEnum[ValidationTokenEnum["Star"] = 13] = "Star"; + ValidationTokenEnum[ValidationTokenEnum["OpenCurlyBrace"] = 14] = "OpenCurlyBrace"; + ValidationTokenEnum[ValidationTokenEnum["CloseCurlyBrace"] = 15] = "CloseCurlyBrace"; + ValidationTokenEnum[ValidationTokenEnum["HashMark"] = 16] = "HashMark"; + ValidationTokenEnum[ValidationTokenEnum["QuestionMark"] = 17] = "QuestionMark"; + ValidationTokenEnum[ValidationTokenEnum["Function"] = 18] = "Function"; + ValidationTokenEnum[ValidationTokenEnum["Number"] = 19] = "Number"; + ValidationTokenEnum[ValidationTokenEnum["Whitespace"] = 20] = "Whitespace"; + ValidationTokenEnum[ValidationTokenEnum["Parenthesis"] = 21] = "Parenthesis"; + ValidationTokenEnum[ValidationTokenEnum["Bracket"] = 22] = "Bracket"; + ValidationTokenEnum[ValidationTokenEnum["Block"] = 23] = "Block"; + ValidationTokenEnum[ValidationTokenEnum["Plus"] = 24] = "Plus"; + ValidationTokenEnum[ValidationTokenEnum["Separator"] = 25] = "Separator"; + ValidationTokenEnum[ValidationTokenEnum["Exclamation"] = 26] = "Exclamation"; + ValidationTokenEnum[ValidationTokenEnum["Ampersand"] = 27] = "Ampersand"; + ValidationTokenEnum[ValidationTokenEnum["PipeToken"] = 28] = "PipeToken"; + ValidationTokenEnum[ValidationTokenEnum["ColumnToken"] = 29] = "ColumnToken"; + ValidationTokenEnum[ValidationTokenEnum["AmpersandToken"] = 30] = "AmpersandToken"; + ValidationTokenEnum[ValidationTokenEnum["Parens"] = 31] = "Parens"; + ValidationTokenEnum[ValidationTokenEnum["PseudoClassToken"] = 32] = "PseudoClassToken"; + ValidationTokenEnum[ValidationTokenEnum["PseudoClassFunctionToken"] = 33] = "PseudoClassFunctionToken"; + ValidationTokenEnum[ValidationTokenEnum["StringToken"] = 34] = "StringToken"; + ValidationTokenEnum[ValidationTokenEnum["AtRuleDefinition"] = 35] = "AtRuleDefinition"; + ValidationTokenEnum[ValidationTokenEnum["DeclarationNameToken"] = 36] = "DeclarationNameToken"; + ValidationTokenEnum[ValidationTokenEnum["DeclarationDefinitionToken"] = 37] = "DeclarationDefinitionToken"; + ValidationTokenEnum[ValidationTokenEnum["SemiColon"] = 38] = "SemiColon"; + ValidationTokenEnum[ValidationTokenEnum["Character"] = 39] = "Character"; + ValidationTokenEnum[ValidationTokenEnum["InfinityToken"] = 40] = "InfinityToken"; + ValidationTokenEnum[ValidationTokenEnum["LessThan"] = 41] = "LessThan"; + ValidationTokenEnum[ValidationTokenEnum["GreaterThan"] = 42] = "GreaterThan"; + /** + * end of token stream + */ + ValidationTokenEnum[ValidationTokenEnum["EOF"] = 43] = "EOF"; + /** + * optional group or tokens, used to group validation tokens + * + * ```ts + * // #? , -> [#? ,]? + * // , ]#? -> [, ]#?]? + * ``` + */ + ValidationTokenEnum[ValidationTokenEnum["OptionalGroupToken"] = 44] = "OptionalGroupToken"; + /** + * dimension token + * + * ```ts + * //