Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "[email protected]",
Expand Down
4 changes: 3 additions & 1 deletion src/css/syntax/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[][] = [];
Expand Down
3 changes: 3 additions & 0 deletions src/css/types/__tests__/color-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
83 changes: 74 additions & 9 deletions src/css/types/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Color> = {
Expand Down Expand Up @@ -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;
Expand All @@ -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 =>
Expand Down