feat(css): Add HSL/HSLA support (#7730)

This commit is contained in:
Bundyo (Kamen Bundev)
2019-09-13 13:49:35 +03:00
committed by Manol Donev
parent f438ad7d69
commit 3cabdde05f
3 changed files with 111 additions and 15 deletions

View File

@ -1,6 +1,7 @@
import * as definition from ".";
import * as definition from ".";
import * as types from "../utils/types";
import * as knownColors from "./known-colors";
import { convertHSLToRGBColor } from "tns-core-modules/css/parser";
const SHARP = "#";
const HEX_REGEX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)|(^#[0-9A-F]{8}$)/i;
@ -18,6 +19,8 @@ export class Color implements definition.Color {
if (types.isString(arg)) {
if (isRgbOrRgba(arg)) {
this._argb = argbFromRgbOrRgba(arg);
} else if (isHslOrHsla(arg)) {
this._argb = argbFromHslOrHsla(arg);
} else if (knownColors.isKnownName(arg)) {
// The parameter is a known color name
const hex = knownColors.getKnownColor(arg);
@ -127,7 +130,7 @@ export class Color implements definition.Color {
return true;
}
return HEX_REGEX.test(value) || isRgbOrRgba(value);
return HEX_REGEX.test(value) || isRgbOrRgba(value) || isHslOrHsla(value);
}
private _componentToHex(component: number): string {
@ -162,33 +165,58 @@ function isRgbOrRgba(value: string): boolean {
return (toLower.indexOf("rgb(") === 0 || toLower.indexOf("rgba(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
}
function argbFromRgbOrRgba(value: string): number {
function isHslOrHsla(value: string): boolean {
const toLower = value.toLowerCase();
const parts = toLower.replace("rgba(", "").replace("rgb(", "").replace(")", "").trim().split(",");
let r = 255;
let g = 255;
let b = 255;
return (toLower.indexOf("hsl(") === 0 || toLower.indexOf("hsla(") === 0) && toLower.indexOf(")") === (toLower.length - 1);
}
function parseColorWithAlpha(value: string): any {
const toLower = value.toLowerCase();
const parts = toLower.replace(/(rgb|hsl)a?\(/, "")
.replace(")", "")
.trim().split(",");
let f = 255;
let s = 255;
let t = 255;
let a = 255;
if (parts[0]) {
r = parseInt(parts[0].trim());
f = parseInt(parts[0].trim());
}
if (parts[1]) {
g = parseInt(parts[1].trim());
s = parseInt(parts[1].trim());
}
if (parts[2]) {
b = parseInt(parts[2].trim());
t = parseInt(parts[2].trim());
}
if (parts[3]) {
a = Math.round(parseFloat(parts[3].trim()) * 255);
}
return { f, s, t, a };
}
function argbFromRgbOrRgba(value: string): number {
const { f: r, s: g, t: b, a } = parseColorWithAlpha(value);
return (a & 0xFF) * 0x01000000
+ (r & 0xFF) * 0x00010000
+ (g & 0xFF) * 0x00000100
+ (b & 0xFF) * 0x00000001;
+ (b & 0xFF);
}
function argbFromHslOrHsla(value: string): number {
const { f: h, s: s, t: l, a } = parseColorWithAlpha(value);
const { r, g, b } = convertHSLToRGBColor(h, s, l);
return (a & 0xFF) * 0x01000000
+ (r & 0xFF) * 0x00010000
+ (g & 0xFF) * 0x00000100
+ (b & 0xFF);
}

View File

@ -84,7 +84,7 @@ export function parseHexColor(text: string, start: number = 0): Parsed<ARGB> {
function rgbaToArgbNumber(r: number, g: number, b: number, a: number = 1): number | undefined {
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + (b * 0x000001);
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + b;
} else {
return null;
}
@ -116,6 +116,67 @@ export function parseRGBAColor(text: string, start: number = 0): Parsed<ARGB> {
return { start, end, value };
}
export function convertHSLToRGBColor(hue: number, saturation: number, lightness: number): { r: number; g: number; b: number; } {
// Per formula it will be easier if hue is divided to 60° and saturation to 100 beforehand
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
hue /= 60;
lightness /= 100;
let chroma = (1 - Math.abs(2 * lightness - 1)) * saturation / 100,
X = chroma * (1 - Math.abs(hue % 2 - 1)),
// Add lightness match to all RGB components beforehand
{ m: r, m: g, m: b } = { m: lightness - chroma / 2 };
if (0 <= hue && hue < 1) { r += chroma; g += X; }
else if (hue < 2) { r += X; g += chroma; }
else if (hue < 3) { g += chroma; b += X; }
else if (hue < 4) { g += X; b += chroma; }
else if (hue < 5) { r += X; b += chroma; }
else if (hue < 6) { r += chroma; b += X; }
return {
r: Math.round(r * 0xFF),
g: Math.round(g * 0xFF),
b: Math.round(b * 0xFF)
};
}
function hslaToArgbNumber(h: number, s: number, l: number, a: number = 1): number | undefined {
let { r, g, b } = convertHSLToRGBColor(h, s, l);
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255 && a >= 0 && a <= 1) {
return (Math.round(a * 0xFF) * 0x01000000) + (r * 0x010000) + (g * 0x000100) + b;
} else {
return null;
}
}
const hslColorRegEx = /\s*(hsl\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*\))/gy;
export function parseHSLColor(text: string, start: number = 0): Parsed<ARGB> {
hslColorRegEx.lastIndex = start;
const result = hslColorRegEx.exec(text);
if (!result) {
return null;
}
const end = hslColorRegEx.lastIndex;
const value = result[1] && hslaToArgbNumber(parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]));
return { start, end, value };
}
const hslaColorRegEx = /\s*(hsla\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*,\s*([01]?\.?\d*)\s*\))/gy;
export function parseHSLAColor(text: string, start: number = 0): Parsed<ARGB> {
hslaColorRegEx.lastIndex = start;
const result = hslaColorRegEx.exec(text);
if (!result) {
return null;
}
const end = hslaColorRegEx.lastIndex;
const value = hslaToArgbNumber(parseFloat(result[2]), parseFloat(result[3]), parseFloat(result[4]), parseFloat(result[5]));
return { start, end, value };
}
export enum colors {
transparent = 0x00000000,
aliceblue = 0xFFF0F8FF,
@ -280,7 +341,12 @@ export function parseColorKeyword(value, start: number, keyword = parseKeyword(v
}
export function parseColor(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<ARGB> {
return parseHexColor(value, start) || parseColorKeyword(value, start, keyword) || parseRGBColor(value, start) || parseRGBAColor(value, start);
return parseHexColor(value, start) ||
parseColorKeyword(value, start, keyword) ||
parseRGBColor(value, start) ||
parseRGBAColor(value, start) ||
parseHSLColor(value, start) ||
parseHSLAColor(value, start);
}
const keywordRegEx = /\s*([a-z][\w\-]*)\s*/giy;

View File

@ -56,6 +56,8 @@ describe("css", () => {
test(parseColor, " #85456789 ", { start: 0, end: 12, value: 0x85456789 });
test(parseColor, " rgb(255, 8, 128) ", { start: 0, end: 18, value: 0xFFFF0880 });
test(parseColor, " rgba(255, 8, 128, 0.5) ", { start: 0, end: 24, value: 0x80FF0880 });
test(parseColor, " hsl(330.9, 100%, 51.6%) ", { start: 0, end: 25, value: 0xFFFF0880 });
test(parseColor, " hsla(330.9, 100%, 51.6%, 0.5) ", { start: 0, end: 31, value: 0x80FF0880 });
test(parseColor, "#FF0000 url(lucky.gif)", 8, null);
test(parseColor, "url(lucky.gif) #FF0000 repeat", 15, { start: 15, end: 23, value: 0xFFFF0000 });
});