8000 feat(css): Add HSL/HSLA support by bundyo · Pull Request #7730 · NativeScript/NativeScript · GitHub
[go: up one dir, main page]

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 13, 2019
50 changes: 39 additions & 11 deletions tns-core-modules/color/color-common.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();

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("rgba(", "").replace("rgb(", "").replace(")", "").trim().split(",");
const parts = toLower.replace(/(rgb|hsl)a?\(/, "")
.replace(")", "")
.trim().split(",");

let r = 255;
let g = 255;
let b = 255;
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);
}

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) * 0x00000001;
+ (b & 0xFF);
}
70 changes: 68 additions & 2 deletions tns-core-modules/css/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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; 10000 }
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,
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions unit-tests/css/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
Expand Down Expand Up @@ -175,11 +177,11 @@ describe("css", () => {
test(parseSelector, `[src ${attributeTest} "val"]`, { start: 0, end: 12 + attributeTest.length, value: [[[{ type: "[]", property: "src", test: attributeTest, value: "val"}], undefined]]});
});
test(parseSelector, "listview > .image", { start: 0, end: 17, value: [
[[{ type: "", identifier: "listview"}], ">"],
[[{ type: "", identifier: "listview"}], ">"],
[[{ type: ".", identifier: "image"}], undefined]
]});
test(parseSelector, "listview .image", { start: 0, end: 16, value: [
[[{ type: "", identifier: "listview"}], " "],
[[{ type: "", identifier: "listview"}], " "],
[[{ type: ".", identifier: "image"}], undefined]
]});
test(parseSelector, "button:hover", { start: 0, end: 12, value: [[[{ type: "", identifier: "button" }, { type: ":", identifier: "hover"}], undefined]]});
Expand Down
0