-
-
Save u1f992/136c413a14c5775ce28e5f64b73d59f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* https://drafts.csswg.org/css-color-5/#device-cmyk | |
* | |
* @param {string} input | |
* @returns {{c:number,m:number,y:number,k:number,a:number}|null} | |
*/ | |
function parseDeviceCmyk(input) { | |
/** | |
* https://www.w3.org/TR/css-syntax/#newline | |
*/ | |
const newline = /\x0a/; | |
/** | |
* https://www.w3.org/TR/css-syntax/#whitespace | |
*/ | |
const whitespace = new RegExp(`(?:${newline.source})|\x09|\x20`); | |
const comment = /\/\*[\s\S]*?\*\//; | |
/** | |
* FIXME: どこかで定義されていないか確認する | |
*/ | |
const tokenSeparator = new RegExp( | |
`(?:${whitespace.source})|(?:${comment.source})` | |
); | |
/** | |
* A hash mark (#) indicates that the preceding type, word, or group occurs one or more times, separated by comma tokens (which may optionally be surrounded by [white space](https://www.w3.org/TR/css-syntax/#whitespace) and/or comments). It may optionally be followed by the curly brace forms, above, to indicate precisely how many times the repetition occurs, like `<length>#{1,4}`. - https://drafts.csswg.org/css-values-4/#mult-comma | |
*/ | |
const multComma = (/** @type {RegExp} */ re, /** @type {number} */ a) => { | |
return new RegExp( | |
Array(a) | |
.fill(`(${re.source})`) | |
.join(`(?:${tokenSeparator.source})*?,(?:${tokenSeparator.source})*?`) | |
); | |
}; | |
/** | |
* A single number in curly braces ({A}) indicates that the preceding type, word, or group occurs A times. - https://drafts.csswg.org/css-values-4/#mult-num | |
* | |
* FIXME: "occurs A times"の意味がよくわからない。空白あるいはコメントを1つ以上挟む? | |
*/ | |
const multNum = (/** @type {RegExp} */ re, /** @type {number} */ a) => { | |
return new RegExp( | |
Array(a).fill(`(${re.source})`).join(`(?:${tokenSeparator.source})+`) | |
); | |
}; | |
/** | |
* When written literally, a number is either an [integer](https://drafts.csswg.org/css-values-4/#integer), or zero or more decimal digits followed by a dot (.) followed by one or more decimal digits; optionally, it can be concluded by the letter "e" or "E" followed by an integer indicating the base-ten exponent in [scientific notation](https://en.wikipedia.org/wiki/Scientific_notation). - https://drafts.csswg.org/css-values-4/#number-value | |
*/ | |
const number = /(?:(?:[\+-]?\d+?)|(?:[\+-]?\d*?\.\d+?))(?:[eE][\+-]?\d+?)?/; | |
/** | |
* When written literally, a percentage consists of a [number](https://drafts.csswg.org/css-values-4/#number) immediately followed by a percent sign %. - https://drafts.csswg.org/css-values-4/#percentage-value | |
*/ | |
const percentage = new RegExp(`(?:${number.source})%`); | |
/** | |
* https://drafts.csswg.org/css-color-4/#typedef-color-alpha-value | |
*/ | |
const alphaValue = new RegExp( | |
`(?:${number.source})|(?:${percentage.source})` | |
); | |
/** | |
* `<cmyk-component> = <number> | <percentage> | none` | |
* https://drafts.csswg.org/css-color-5/#device-cmyk | |
*/ | |
const cmykComponent = new RegExp( | |
`(?:${number.source})|(?:${percentage.source})|(?:none)` | |
); | |
/** | |
* `<legacy-device-cmyk-syntax> = device-cmyk( <number>#{4} )` | |
* https://drafts.csswg.org/css-color-5/#device-cmyk | |
*/ | |
const legacyDeviceCmykSyntax = new RegExp( | |
`device-cmyk\\((?:${tokenSeparator.source})*(?:${ | |
multComma(number, 4).source | |
})(?:${tokenSeparator.source})*\\)` | |
); | |
/** | |
* `<modern-device-cmyk-syntax> = device-cmyk( <cmyk-component>{4} [ / [ <alpha-value> | none ] ]? )` | |
* https://drafts.csswg.org/css-color-5/#device-cmyk | |
*/ | |
const modernDeviceCmykSyntax = new RegExp( | |
`device-cmyk\\((?:${tokenSeparator.source})*(?:${ | |
multNum(cmykComponent, 4).source | |
})(?:(?:${tokenSeparator.source})*/(?:${tokenSeparator.source})*((?:${ | |
alphaValue.source | |
})|none))?(?:${tokenSeparator.source})*\\)` | |
); | |
/** | |
* `device-cmyk() = <legacy-device-cmyk-syntax> | <modern-device-cmyk-syntax>` | |
* https://drafts.csswg.org/css-color-5/#device-cmyk | |
* | |
* Legacy | |
* - c: ret[1] | |
* - m: ret[2] | |
* - y: ret[3] | |
* - k: ret[4] | |
* | |
* Modern | |
* - c: ret[5] | |
* - m: ret[6] | |
* - y: ret[7] | |
* - k: ret[8] | |
* - a?: ret[9] | |
*/ | |
const deviceCmyk = new RegExp( | |
`^(?:${legacyDeviceCmykSyntax.source})|(?:${modernDeviceCmykSyntax.source})$` | |
); | |
const parseSaturate = (/** @type {string} */ val) => | |
Math.max( | |
0, | |
Math.min( | |
val.endsWith("%") | |
? parseFloat(val.slice(0, -1)) / 100 | |
: parseFloat(val), | |
1 | |
) | |
); | |
const match = deviceCmyk.exec(input); | |
return !match | |
? null | |
: match[1] | |
? { | |
c: parseSaturate(match[1]), | |
m: parseSaturate(match[2]), | |
y: parseSaturate(match[3]), | |
k: parseSaturate(match[4]), | |
a: 1, | |
} | |
: { | |
// FIXME: `none`の場合の挙動が不明 | |
c: match[5] === "none" ? 0 : parseSaturate(match[5]), | |
m: match[6] === "none" ? 0 : parseSaturate(match[6]), | |
y: match[7] === "none" ? 0 : parseSaturate(match[7]), | |
k: match[8] === "none" ? 0 : parseSaturate(match[8]), | |
a: | |
typeof match[9] === "undefined" || match[9] === "none" | |
? 1 | |
: parseSaturate(match[9]), | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment