diff --git a/package-lock.json b/package-lock.json index f972f7112..b208d948b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,12 @@ { "name": "html2canvas", - "version": "1.4.1", + "version": "1.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.4.0", + "name": "html2canvas", + "version": "1.4.2", "license": "MIT", "dependencies": { "css-line-break": "^2.1.0", diff --git a/package.json b/package.json index cefd4369a..c5557c6f6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "module": "dist/html2canvas.esm.js", "typings": "dist/types/index.d.ts", "browser": "dist/html2canvas.js", - "version": "1.4.1", + "version": "1.4.2", "author": { "name": "Niklas von Hertzen", "email": "niklasvh@gmail.com", diff --git a/src/css/syntax/parser.ts b/src/css/syntax/parser.ts index 7d65779a3..8f30f2173 100644 --- a/src/css/syntax/parser.ts +++ b/src/css/syntax/parser.ts @@ -150,7 +150,9 @@ export const isIdentWithValue = (token: CSSValue, value: string): boolean => export const nonWhiteSpace = (token: CSSValue): boolean => token.type !== TokenType.WHITESPACE_TOKEN; export const nonFunctionArgSeparator = (token: CSSValue): boolean => - token.type !== TokenType.WHITESPACE_TOKEN && token.type !== TokenType.COMMA_TOKEN; + token.type !== TokenType.WHITESPACE_TOKEN && + token.type !== TokenType.COMMA_TOKEN && + token.type !== TokenType.DELIM_TOKEN; export const parseFunctionArgs = (tokens: CSSValue[]): CSSValue[][] => { const args: CSSValue[][] = []; diff --git a/src/css/types/__tests__/color-tests.ts b/src/css/types/__tests__/color-tests.ts index 2976c3c26..8ea0b2452 100644 --- a/src/css/types/__tests__/color-tests.ts +++ b/src/css/types/__tests__/color-tests.ts @@ -40,6 +40,9 @@ describe('types', () => { it('hsl(.75turn, 60%, 70%)', () => strictEqual(parse('hsl(.75turn, 60%, 70%)'), parse('rgb(178,132,224)'))); it('hsla(.75turn, 60%, 70%, 50%)', () => strictEqual(parse('hsl(.75turn, 60%, 70%, 50%)'), parse('rgba(178,132,224, 0.5)'))); + it('lch(29.2345% 44.2 27 / 0.2)', () => + strictEqual(parse('lch(29.2345% 44.2 27 / 0.2)'), pack(255, 148, 143, 0.2))); + it('lch(76.5 4.24 49.5)', () => strictEqual(parse('lch(76.5 4.24 49.5)'), pack(212, 182, 175, 1))); }); describe('util', () => { describe('isTransparent', () => { diff --git a/src/css/types/color.ts b/src/css/types/color.ts index 29af9ce02..e6e019839 100644 --- a/src/css/types/color.ts +++ b/src/css/types/color.ts @@ -4,6 +4,7 @@ import {ITypeDescriptor} from '../ITypeDescriptor'; import {angle, deg} from './angle'; import {getAbsoluteValue, isLengthPercentage} from './length-percentage'; import {Context} from '../../core/context'; + export type Color = number; export const color: ITypeDescriptor = { @@ -121,6 +122,20 @@ function hue2rgb(t1: number, t2: number, hue: number): number { } } +function hsl2rgb(h: number, s: number, l: number, a: number): number { + if (s === 0) { + return pack(l * 255, l * 255, l * 255, 1); + } + + const t2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + + const t1 = l * 2 - t2; + const r = hue2rgb(t1, t2, h + 1 / 3); + const g = hue2rgb(t1, t2, h); + const b = hue2rgb(t1, t2, h - 1 / 3); + return pack(r * 255, g * 255, b * 255, a); +} + const hsl = (context: Context, args: CSSValue[]): number => { const tokens = args.filter(nonFunctionArgSeparator); const [hue, saturation, lightness, alpha] = tokens; @@ -130,26 +145,76 @@ const hsl = (context: Context, args: CSSValue[]): number => { const l = isLengthPercentage(lightness) ? lightness.number / 100 : 0; const a = typeof alpha !== 'undefined' && isLengthPercentage(alpha) ? getAbsoluteValue(alpha, 1) : 1; - if (s === 0) { - return pack(l * 255, l * 255, l * 255, 1); + return hsl2rgb(h, s, l, a); +}; + +const lch = (_context: Context, args: CSSValue[]): number => { + const tokens = args.filter(nonFunctionArgSeparator); + + if (tokens.length === 4) { + const [lightness, chroma, hue, alpha] = tokens.map(getTokenColorValue); + const [r, g, b] = lchToRgb(lightness, chroma, hue); + return pack(r, g, b, alpha); } - const t2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + if (tokens.length === 3) { + const [lightness, chroma, hue] = tokens.map(getTokenColorValue); + const [r, g, b] = lchToRgb(lightness, chroma, hue); + return pack(r, g, b, 1); + } - const t1 = l * 2 - t2; - const r = hue2rgb(t1, t2, h + 1 / 3); - const g = hue2rgb(t1, t2, h); - const b = hue2rgb(t1, t2, h - 1 / 3); - return pack(r * 255, g * 255, b * 255, a); + return pack(255, 255, 255, 1); }; +function lchToRgb(l: number, c: number, h: number): [number, number, number] { + // Convert degrees to radians for hue + const rad = (h * Math.PI) / 180; + + // Convert LCH to LAB + const a = c * Math.cos(rad); + const b = c * Math.sin(rad); + + // Convert LAB to XYZ + const y = (l + 16) / 116; + const x = a / 500 + y; + const z = y - b / 200; + + // Convert XYZ to RGB + const red = pivotRgb(x) * 3.2406 + pivotRgb(y) * -1.5372 + pivotRgb(z) * -0.4986; + const green = pivotRgb(x) * -0.9689 + pivotRgb(y) * 1.8758 + pivotRgb(z) * 0.0415; + const blue = pivotRgb(x) * 0.0557 + pivotRgb(y) * -0.204 + pivotRgb(z) * 1.057; + + // Convert to sRGB + const sRgbR = red > 0.0031308 ? 1.055 * Math.pow(red, 1 / 2.4) - 0.055 : 12.92 * red; + const sRgbG = green > 0.0031308 ? 1.055 * Math.pow(green, 1 / 2.4) - 0.055 : 12.92 * green; + const sRgbB = blue > 0.0031308 ? 1.055 * Math.pow(blue, 1 / 2.4) - 0.055 : 12.92 * blue; + + // Clamp RGB values to [0, 1] range + const rgbR = Math.min(Math.max(0, sRgbR), 1); + const rgbG = Math.min(Math.max(0, sRgbG), 1); + const rgbB = Math.min(Math.max(0, sRgbB), 1); + + // Scale to [0, 255] range and round to integers + const finalR = Math.round(rgbR * 255); + const finalG = Math.round(rgbG * 255); + const finalB = Math.round(rgbB * 255); + + // Return RGB values as an array + return [finalR, finalG, finalB]; +} + +function pivotRgb(value: number): number { + return value > 0.206893034 ? Math.pow(value, 3) : (value - 4 / 29) / 7.787037; +} + const SUPPORTED_COLOR_FUNCTIONS: { [key: string]: (context: Context, args: CSSValue[]) => number; } = { hsl: hsl, hsla: hsl, rgb: rgb, - rgba: rgb + rgba: rgb, + lch: lch }; export const parseColor = (context: Context, value: string): Color =>