perf: faster color parsing (#9001)

This commit is contained in:
Martin Guillon
2020-11-10 02:00:22 +01:00
committed by GitHub
parent d30b22c9a3
commit c569236357
3 changed files with 196 additions and 341 deletions

View File

@@ -1,3 +1,5 @@
import { getKnownColor } from "../color/known-colors";
export type Parsed<V> = { start: number; end: number; value: V };
// Values
@@ -53,7 +55,7 @@ export interface BackgroundPosition {
}
const urlRegEx = /\s*url\((?:('|")([^\1]*)\1|([^\)]*))\)\s*/gy;
export function parseURL(text: string, start: number = 0): Parsed<URL> {
export function parseURL(text: string, start = 0): Parsed<URL> {
urlRegEx.lastIndex = start;
const result = urlRegEx.exec(text);
if (!result) {
@@ -66,14 +68,14 @@ export function parseURL(text: string, start: number = 0): Parsed<URL> {
}
const hexColorRegEx = /\s*#((?:[0-9A-F]{8})|(?:[0-9A-F]{6})|(?:[0-9A-F]{3}))\s*/giy;
export function parseHexColor(text: string, start: number = 0): Parsed<ARGB> {
export function parseHexColor(text: string, start = 0): Parsed<ARGB> {
hexColorRegEx.lastIndex = start;
const result = hexColorRegEx.exec(text);
if (!result) {
return null;
}
const end = hexColorRegEx.lastIndex;
let hex = result[1];
const hex = result[1];
let argb;
if (hex.length === 8) {
argb = parseInt('0x' + hex);
@@ -86,7 +88,7 @@ export function parseHexColor(text: string, start: number = 0): Parsed<ARGB> {
return { start, end, value: argb };
}
function rgbaToArgbNumber(r: number, g: number, b: number, a: number = 1): number | undefined {
function rgbaToArgbNumber(r: number, g: number, b: number, a = 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;
} else {
@@ -95,7 +97,7 @@ function rgbaToArgbNumber(r: number, g: number, b: number, a: number = 1): numbe
}
const rgbColorRegEx = /\s*(rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\))/gy;
export function parseRGBColor(text: string, start: number = 0): Parsed<ARGB> {
export function parseRGBColor(text: string, start = 0): Parsed<ARGB> {
rgbColorRegEx.lastIndex = start;
const result = rgbColorRegEx.exec(text);
if (!result) {
@@ -108,7 +110,7 @@ export function parseRGBColor(text: string, start: number = 0): Parsed<ARGB> {
}
const rgbaColorRegEx = /\s*(rgba\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*,\s*([01]?\.?\d*)\s*\))/gy;
export function parseRGBAColor(text: string, start: number = 0): Parsed<ARGB> {
export function parseRGBAColor(text: string, start = 0): Parsed<ARGB> {
rgbaColorRegEx.lastIndex = start;
const result = rgbaColorRegEx.exec(text);
if (!result) {
@@ -158,8 +160,8 @@ export function convertHSLToRGBColor(hue: number, saturation: number, lightness:
};
}
function hslaToArgbNumber(h: number, s: number, l: number, a: number = 1): number | undefined {
let { r, g, b } = convertHSLToRGBColor(h, s, l);
function hslaToArgbNumber(h: number, s: number, l: number, a = 1): number | undefined {
const { 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;
@@ -169,7 +171,7 @@ function hslaToArgbNumber(h: number, s: number, l: number, a: number = 1): numbe
}
const hslColorRegEx = /\s*(hsl\(\s*([\d.]*)\s*,\s*([\d.]*)%\s*,\s*([\d.]*)%\s*\))/gy;
export function parseHSLColor(text: string, start: number = 0): Parsed<ARGB> {
export function parseHSLColor(text: string, start = 0): Parsed<ARGB> {
hslColorRegEx.lastIndex = start;
const result = hslColorRegEx.exec(text);
if (!result) {
@@ -182,7 +184,7 @@ export function parseHSLColor(text: string, start: number = 0): Parsed<ARGB> {
}
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> {
export function parseHSLAColor(text: string, start = 0): Parsed<ARGB> {
hslaColorRegEx.lastIndex = start;
const result = hslaColorRegEx.exec(text);
if (!result) {
@@ -194,175 +196,24 @@ export function parseHSLAColor(text: string, start: number = 0): Parsed<ARGB> {
return { start, end, value };
}
export enum colors {
transparent = 0x00000000,
aliceblue = 0xfff0f8ff,
antiquewhite = 0xfffaebd7,
aqua = 0xff00ffff,
aquamarine = 0xff7fffd4,
azure = 0xfff0ffff,
beige = 0xfff5f5dc,
bisque = 0xffffe4c4,
black = 0xff000000,
blanchedalmond = 0xffffebcd,
blue = 0xff0000ff,
blueviolet = 0xff8a2be2,
brown = 0xffa52a2a,
burlywood = 0xffdeb887,
cadetblue = 0xff5f9ea0,
chartreuse = 0xff7fff00,
chocolate = 0xffd2691e,
coral = 0xffff7f50,
cornflowerblue = 0xff6495ed,
cornsilk = 0xfffff8dc,
crimson = 0xffdc143c,
cyan = 0xff00ffff,
darkblue = 0xff00008b,
darkcyan = 0xff008b8b,
darkgoldenrod = 0xffb8860b,
darkgray = 0xffa9a9a9,
darkgreen = 0xff006400,
darkgrey = 0xffa9a9a9,
darkkhaki = 0xffbdb76b,
darkmagenta = 0xff8b008b,
darkolivegreen = 0xff556b2f,
darkorange = 0xffff8c00,
darkorchid = 0xff9932cc,
darkred = 0xff8b0000,
darksalmon = 0xffe9967a,
darkseagreen = 0xff8fbc8f,
darkslateblue = 0xff483d8b,
darkslategray = 0xff2f4f4f,
darkslategrey = 0xff2f4f4f,
darkturquoise = 0xff00ced1,
darkviolet = 0xff9400d3,
deeppink = 0xffff1493,
deepskyblue = 0xff00bfff,
dimgray = 0xff696969,
dimgrey = 0xff696969,
dodgerblue = 0xff1e90ff,
firebrick = 0xffb22222,
floralwhite = 0xfffffaf0,
forestgreen = 0xff228b22,
fuchsia = 0xffff00ff,
gainsboro = 0xffdcdcdc,
ghostwhite = 0xfff8f8ff,
gold = 0xffffd700,
goldenrod = 0xffdaa520,
gray = 0xff808080,
green = 0xff008000,
greenyellow = 0xffadff2f,
grey = 0xff808080,
honeydew = 0xfff0fff0,
hotpink = 0xffff69b4,
indianred = 0xffcd5c5c,
indigo = 0xff4b0082,
ivory = 0xfffffff0,
khaki = 0xfff0e68c,
lavender = 0xffe6e6fa,
lavenderblush = 0xfffff0f5,
lawngreen = 0xff7cfc00,
lemonchiffon = 0xfffffacd,
lightblue = 0xffadd8e6,
lightcoral = 0xfff08080,
lightcyan = 0xffe0ffff,
lightgoldenrodyellow = 0xfffafad2,
lightgray = 0xffd3d3d3,
lightgreen = 0xff90ee90,
lightgrey = 0xffd3d3d3,
lightpink = 0xffffb6c1,
lightsalmon = 0xffffa07a,
lightseagreen = 0xff20b2aa,
lightskyblue = 0xff87cefa,
lightslategray = 0xff778899,
lightslategrey = 0xff778899,
lightsteelblue = 0xffb0c4de,
lightyellow = 0xffffffe0,
lime = 0xff00ff00,
limegreen = 0xff32cd32,
linen = 0xfffaf0e6,
magenta = 0xffff00ff,
maroon = 0xff800000,
mediumaquamarine = 0xff66cdaa,
mediumblue = 0xff0000cd,
mediumorchid = 0xffba55d3,
mediumpurple = 0xff9370db,
mediumseagreen = 0xff3cb371,
mediumslateblue = 0xff7b68ee,
mediumspringgreen = 0xff00fa9a,
mediumturquoise = 0xff48d1cc,
mediumvioletred = 0xffc71585,
midnightblue = 0xff191970,
mintcream = 0xfff5fffa,
mistyrose = 0xffffe4e1,
moccasin = 0xffffe4b5,
navajowhite = 0xffffdead,
navy = 0xff000080,
oldlace = 0xfffdf5e6,
olive = 0xff808000,
olivedrab = 0xff6b8e23,
orange = 0xffffa500,
orangered = 0xffff4500,
orchid = 0xffda70d6,
palegoldenrod = 0xffeee8aa,
palegreen = 0xff98fb98,
paleturquoise = 0xffafeeee,
palevioletred = 0xffdb7093,
papayawhip = 0xffffefd5,
peachpuff = 0xffffdab9,
peru = 0xffcd853f,
pink = 0xffffc0cb,
plum = 0xffdda0dd,
powderblue = 0xffb0e0e6,
purple = 0xff800080,
rebeccapurple = 0xff663399,
red = 0xffff0000,
rosybrown = 0xffbc8f8f,
royalblue = 0xff4169e1,
saddlebrown = 0xff8b4513,
salmon = 0xfffa8072,
sandybrown = 0xfff4a460,
seagreen = 0xff2e8b57,
seashell = 0xfffff5ee,
sienna = 0xffa0522d,
silver = 0xffc0c0c0,
skyblue = 0xff87ceeb,
slateblue = 0xff6a5acd,
slategray = 0xff708090,
slategrey = 0xff708090,
snow = 0xfffffafa,
springgreen = 0xff00ff7f,
steelblue = 0xff4682b4,
tan = 0xffd2b48c,
teal = 0xff008080,
thistle = 0xffd8bfd8,
tomato = 0xffff6347,
turquoise = 0xff40e0d0,
violet = 0xffee82ee,
wheat = 0xfff5deb3,
white = 0xffffffff,
whitesmoke = 0xfff5f5f5,
yellow = 0xffffff00,
yellowgreen = 0xff9acd32,
}
export function parseColorKeyword(value, start: number, keyword = parseKeyword(value, start)): Parsed<ARGB> {
if (keyword && keyword.value in colors) {
const parseColor = keyword && getKnownColor(keyword.value);
if (parseColor !== undefined) {
const end = keyword.end;
const value = colors[keyword.value];
const value = parseColor;
return { start, end, value };
}
return null;
}
export function parseColor(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<ARGB> {
export function parseColor(value: string, start = 0, keyword = parseKeyword(value, start)): Parsed<ARGB> {
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;
function parseKeyword(text: string, start: number = 0): Parsed<Keyword> {
function parseKeyword(text: string, start = 0): Parsed<Keyword> {
keywordRegEx.lastIndex = start;
const result = keywordRegEx.exec(text);
if (!result) {
@@ -375,7 +226,7 @@ function parseKeyword(text: string, start: number = 0): Parsed<Keyword> {
}
const backgroundRepeatKeywords = new Set(['repeat', 'repeat-x', 'repeat-y', 'no-repeat']);
export function parseRepeat(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<BackgroundRepeat> {
export function parseRepeat(value: string, start = 0, keyword = parseKeyword(value, start)): Parsed<BackgroundRepeat> {
if (keyword && backgroundRepeatKeywords.has(keyword.value)) {
const end = keyword.end;
const value = <BackgroundRepeat>keyword.value;
@@ -387,7 +238,7 @@ export function parseRepeat(value: string, start: number = 0, keyword = parseKey
}
const unitRegEx = /\s*([\+\-]?(?:\d+\.\d+|\d+|\.\d+)(?:[eE][\+\-]?\d+)?)([a-zA-Z]+|%)?\s*/gy;
export function parseUnit(text: string, start: number = 0): Parsed<Unit<string>> {
export function parseUnit(text: string, start = 0): Parsed<Unit<string>> {
unitRegEx.lastIndex = start;
const result = unitRegEx.exec(text);
if (!result) {
@@ -400,7 +251,7 @@ export function parseUnit(text: string, start: number = 0): Parsed<Unit<string>>
return { start, end, value: { value, unit } };
}
export function parsePercentageOrLength(text: string, start: number = 0): Parsed<LengthPercentage> {
export function parsePercentageOrLength(text: string, start = 0): Parsed<LengthPercentage> {
const unitResult = parseUnit(text, start);
if (unitResult) {
const { start, end } = unitResult;
@@ -445,7 +296,7 @@ const angleUnitsToRadMap: {
value: turn * Math.PI * 2,
}),
};
export function parseAngle(value: string, start: number = 0): Parsed<Angle> {
export function parseAngle(value: string, start = 0): Parsed<Angle> {
const angleResult = parseUnit(value, start);
if (angleResult) {
const { start, end, value } = angleResult;
@@ -457,7 +308,7 @@ export function parseAngle(value: string, start: number = 0): Parsed<Angle> {
}
const backgroundSizeKeywords = new Set(['auto', 'contain', 'cover']);
export function parseBackgroundSize(value: string, start: number = 0, keyword = parseKeyword(value, start)): Parsed<BackgroundSize> {
export function parseBackgroundSize(value: string, start = 0, keyword = parseKeyword(value, start)): Parsed<BackgroundSize> {
let end = start;
if (keyword && backgroundSizeKeywords.has(keyword.value)) {
end = keyword.end;
@@ -497,7 +348,7 @@ const backgroundPositionKeywordsDirection: {
top: 'y',
bottom: 'y',
};
export function parseBackgroundPosition(text: string, start: number = 0, keyword = parseKeyword(text, start)): Parsed<BackgroundPosition> {
export function parseBackgroundPosition(text: string, start = 0, keyword = parseKeyword(text, start)): Parsed<BackgroundPosition> {
function formatH(align: Parsed<HorizontalAlign>, offset: Parsed<LengthPercentage>) {
if (align.value === 'center') {
return 'center';
@@ -521,7 +372,7 @@ export function parseBackgroundPosition(text: string, start: number = 0, keyword
let end = start;
if (keyword && backgroundPositionKeywords.has(keyword.value)) {
end = keyword.end;
let firstDirection = backgroundPositionKeywordsDirection[keyword.value];
const firstDirection = backgroundPositionKeywordsDirection[keyword.value];
const firstLength = firstDirection !== 'center' && parsePercentageOrLength(text, end);
if (firstLength) {
@@ -531,7 +382,7 @@ export function parseBackgroundPosition(text: string, start: number = 0, keyword
const secondKeyword = parseKeyword(text, end);
if (secondKeyword && backgroundPositionKeywords.has(secondKeyword.value)) {
end = secondKeyword.end;
let secondDirection = backgroundPositionKeywordsDirection[secondKeyword.end];
const secondDirection = backgroundPositionKeywordsDirection[secondKeyword.end];
if (firstDirection === secondDirection && firstDirection !== 'center') {
return null; // Reject pair of both horizontal or both vertical alignments.
@@ -641,7 +492,7 @@ const cornerDirections = {
bottom: (Math.PI * 5) / 4,
},
};
function parseDirection(text: string, start: number = 0): Parsed<Angle> {
function parseDirection(text: string, start = 0): Parsed<Angle> {
directionRegEx.lastIndex = start;
const result = directionRegEx.exec(text);
if (!result) {
@@ -700,7 +551,7 @@ function parseArgumentsList<T>(text: string, start: number, argument: (value: st
}
}
export function parseColorStop(text: string, start: number = 0): Parsed<ColorStop> {
export function parseColorStop(text: string, start = 0): Parsed<ColorStop> {
const color = parseColor(text, start);
if (!color) {
return null;
@@ -721,7 +572,7 @@ export function parseColorStop(text: string, start: number = 0): Parsed<ColorSto
}
const linearGradientStartRegEx = /\s*linear-gradient\s*/gy;
export function parseLinearGradient(text: string, start: number = 0): Parsed<LinearGradient> {
export function parseLinearGradient(text: string, start = 0): Parsed<LinearGradient> {
linearGradientStartRegEx.lastIndex = start;
const lgs = linearGradientStartRegEx.exec(text);
if (!lgs) {
@@ -772,7 +623,7 @@ function parseSlash(text: string, start: number): Parsed<'/'> {
return { start, end, value: '/' };
}
export function parseBackground(text: string, start: number = 0): Parsed<Background> {
export function parseBackground(text: string, start = 0): Parsed<Background> {
const value: any = {};
let end = start;
while (end < text.length) {
@@ -865,7 +716,7 @@ export type SelectorCombinatorPair = [SimpleSelectorSequence, Combinator];
export type Selector = SelectorCombinatorPair[];
const universalSelectorRegEx = /\*/gy;
export function parseUniversalSelector(text: string, start: number = 0): Parsed<UniversalSelector> {
export function parseUniversalSelector(text: string, start = 0): Parsed<UniversalSelector> {
universalSelectorRegEx.lastIndex = start;
const result = universalSelectorRegEx.exec(text);
if (!result) {
@@ -878,7 +729,7 @@ export function parseUniversalSelector(text: string, start: number = 0): Parsed<
const simpleIdentifierSelectorRegEx = /(#|\.|:|\b)((?:[\w_-]|\\.)(?:[\w\d_-]|\\.)*)/guy;
const unicodeEscapeRegEx = /\\([0-9a-fA-F]{1,5}\s|[0-9a-fA-F]{6})/g;
export function parseSimpleIdentifierSelector(text: string, start: number = 0): Parsed<TypeSelector | ClassSelector | IdSelector | PseudoClassSelector> {
export function parseSimpleIdentifierSelector(text: string, start = 0): Parsed<TypeSelector | ClassSelector | IdSelector | PseudoClassSelector> {
simpleIdentifierSelectorRegEx.lastIndex = start;
const result = simpleIdentifierSelectorRegEx.exec(text.replace(unicodeEscapeRegEx, (_, c) => '\\' + String.fromCodePoint(parseInt(c.trim(), 16))));
if (!result) {
@@ -911,7 +762,7 @@ export function parseAttributeSelector(text: string, start: number): Parsed<Attr
return { start, end, value: { type: '[]', property } };
}
export function parseSimpleSelector(text: string, start: number = 0): Parsed<SimpleSelector> {
export function parseSimpleSelector(text: string, start = 0): Parsed<SimpleSelector> {
return parseUniversalSelector(text, start) || parseSimpleIdentifierSelector(text, start) || parseAttributeSelector(text, start);
}
@@ -921,7 +772,7 @@ export function parseSimpleSelectorSequence(text: string, start: number): Parsed
return null;
}
let end = simpleSelector.end;
let value = <SimpleSelectorSequence>[];
const value = <SimpleSelectorSequence>[];
while (simpleSelector) {
value.push(simpleSelector.value);
end = simpleSelector.end;
@@ -932,7 +783,7 @@ export function parseSimpleSelectorSequence(text: string, start: number): Parsed
}
const combinatorRegEx = /\s*(\+|~|>)?\s*/gy;
export function parseCombinator(text: string, start: number = 0): Parsed<Combinator> {
export function parseCombinator(text: string, start = 0): Parsed<Combinator> {
combinatorRegEx.lastIndex = start;
const result = combinatorRegEx.exec(text);
if (!result) {
@@ -945,14 +796,14 @@ export function parseCombinator(text: string, start: number = 0): Parsed<Combina
}
const whiteSpaceRegEx = /\s*/gy;
export function parseSelector(text: string, start: number = 0): Parsed<Selector> {
export function parseSelector(text: string, start = 0): Parsed<Selector> {
let end = start;
whiteSpaceRegEx.lastIndex = end;
const leadingWhiteSpace = whiteSpaceRegEx.exec(text);
if (leadingWhiteSpace) {
end = whiteSpaceRegEx.lastIndex;
}
let value = <Selector>[];
const value = <Selector>[];
let combinator: Parsed<Combinator>;
let expectSimpleSelector = true; // Must have at least one
let pair: SelectorCombinatorPair;
@@ -1092,7 +943,7 @@ interface SimpleBlock extends InputTokenObject {
values: InputToken[];
}
interface AtKeywordToken extends InputTokenObject {}
type AtKeywordToken = InputTokenObject
/**
* CSS parser following relatively close:
@@ -1111,7 +962,7 @@ export class CSS3Parser {
* This method allows us to run and assert the proper working of the tokenizer.
*/
tokenize(): InputToken[] {
let tokens: InputToken[] = [];
const tokens: InputToken[] = [];
let inputToken: InputToken;
do {
inputToken = this.consumeAToken();
@@ -1127,7 +978,7 @@ export class CSS3Parser {
*/
private consumeAToken(): InputToken {
if (this.reconsumedInputToken) {
let result = this.reconsumedInputToken;
const result = this.reconsumedInputToken;
this.reconsumedInputToken = null;
return result;
@@ -1224,7 +1075,7 @@ export class CSS3Parser {
private consumeAHashToken(): InputTokenObject {
this.nextInputCodePointIndex++;
let hashName = this.consumeAName();
const hashName = this.consumeAName();
if (hashName) {
return { type: TokenObjectType.hash, text: '#' + hashName.text };
}
@@ -1429,7 +1280,7 @@ export class CSS3Parser {
private consumeAtKeyword(): InputTokenObject {
this.nextInputCodePointIndex++;
let name = this.consumeAName();
const name = this.consumeAName();
if (name) {
return { type: TokenObjectType.atKeyword, text: name.text };
}
@@ -1559,7 +1410,7 @@ export class CSS3Parser {
let inputToken: InputToken;
while ((inputToken = this.consumeAToken())) {
if (inputToken === '{') {
let block = this.consumeASimpleBlock(inputToken);
const block = this.consumeASimpleBlock(inputToken);
qualifiedRule.block = block;
return qualifiedRule;
@@ -1739,7 +1590,7 @@ export class CSSNativeScript {
let reading: 'property' | 'value' = 'property';
for (let i = 0; i < declarationsInputTokens.length; i++) {
let inputToken = declarationsInputTokens[i];
const inputToken = declarationsInputTokens[i];
if (reading === 'property') {
if (inputToken === ':') {
reading = 'value';
@@ -1773,7 +1624,7 @@ export class CSSNativeScript {
}
private preludeToSelectorsStringArray(prelude: InputToken[]): string[] {
let selectors = [];
const selectors = [];
let selector = '';
prelude.forEach((inputToken) => {
if (typeof inputToken === 'string') {