From afbcc17d1279180db28a58044dca39b6e909c6b9 Mon Sep 17 00:00:00 2001 From: akianonymus Date: Sat, 25 Feb 2023 19:50:18 +0530 Subject: feat: Improve hsl parser follow https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl add support for deg, turn and all alpha values merge hsl and hsla into one parser less regex computation --- lua/colorizer/color.lua | 111 ++++++++++++++++++++++++++++------------------ lua/colorizer/matcher.lua | 8 +++- 2 files changed, 73 insertions(+), 46 deletions(-) (limited to 'lua') diff --git a/lua/colorizer/color.lua b/lua/colorizer/color.lua index 9cfac2a..37f8f30 100644 --- a/lua/colorizer/color.lua +++ b/lua/colorizer/color.lua @@ -86,76 +86,99 @@ function color.hsl_to_rgb(h, s, l) 255 * color.hue_to_rgb(p, q, h - 1 / 3) end +local CSS_HSLA_FN_MINIMUM_LENGTH = #"hsla(0,0%,0%)" - 1 local CSS_HSL_FN_MINIMUM_LENGTH = #"hsl(0,0%,0%)" - 1 ----Parse for hsl() css function and return rgb hex. +---Parse for hsl() hsla() css function and return rgb hex. +-- For more info: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl ---@param line string: Line to parse ---@param i number: Index of line from where to start parsing ----@return number|nil: Index of line where the hsl function ended +---@param opts table: Values passed from matchers like prefix +---@return number|nil: Index of line where the hsla/hsl function ended ---@return string|nil: rgb hex value -function color.hsl_function_parser(line, i) - if #line < i + CSS_HSL_FN_MINIMUM_LENGTH then - return - end - local h, s, l, match_end = line:sub(i):match "^hsl%(%s*(%d+)%s*,%s*(%d+)%%%s*,%s*(%d+)%%%s*%)()" - if not match_end then - h, s, l, match_end = line:sub(i):match "^hsl%(%s*(%d+)%s+(%d+)%%%s+(%d+)%%%s*%)()" - if not match_end then - return - end +function color.hsl_function_parser(line, i, opts) + local min_len = CSS_HSLA_FN_MINIMUM_LENGTH + local min_commas, min_spaces = 2, 2 + local pattern = "^" + .. opts.prefix + .. "%(%s*([.%d]+)([deg]*)([turn]*)(,?)(%s?)%s*(%d+)%%(,?)(%s?)%s*(%d+)%%%s*(/?,?)%s*([.%d]*)([%%]?)%s*%)()" + + if opts.prefix == "hsl" then + min_len = CSS_HSL_FN_MINIMUM_LENGTH end - h = tonumber(h) - if h > 360 then + + if #line < i + min_len then return end - s = tonumber(s) - if s > 100 then + + local h, deg, turn, csep1, ssep1, s, csep2, ssep2, l, sep3, a, percent_sign, match_end = line:sub(i):match(pattern) + if not match_end then return end - l = tonumber(l) - if l > 100 then - return + if a == "" then + a = nil + else + min_commas = min_commas + 1 end - local r, g, b = color.hsl_to_rgb(h / 360, s / 100, l / 100) - if r == nil or g == nil or b == nil then + + -- the text after hue should be either deg or empty + if not ((deg == "") or (deg == "deg") or (turn == "turn")) then return end - local rgb_hex = string.format("%02x%02x%02x", r, g, b) - return match_end - 1, rgb_hex -end -local CSS_HSLA_FN_MINIMUM_LENGTH = #"hsla(0,0%,0%,0)" - 1 ----Parse for hsl() css function and return rgb hex. ----@param line string: Line to parse ----@param i number: Index of line from where to start parsing ----@return number|nil: Index of line where the hsla function ended ----@return string|nil: rgb hex value -function color.hsla_function_parser(line, i) - if #line < i + CSS_HSLA_FN_MINIMUM_LENGTH then + local c_seps = ("%s%s%s"):format(csep1, csep2, sep3) + local s_seps = ("%s%s"):format(ssep1, ssep2) + -- comma separator syntax + if c_seps:match "," then + if not (#c_seps:match ",*" == min_commas) then + return + end + -- space separator syntax with decimal or percentage alpha + elseif #s_seps:match "%s*" >= min_spaces then + if a then + if not (c_seps == "/") then + return + end + end + else return end - local h, s, l, a, match_end = line:sub(i):match "^hsla%(%s*(%d+)%s*,%s*(%d+)%%%s*,%s*(%d+)%%%s*,%s*([.%d]+)%s*%)()" - if not match_end then - h, s, l, a, match_end = line:sub(i):match "^hsla%(%s*(%d+)%s+(%d+)%%%s+(%d+)%%%s+([.%d]+)%s*%)()" - if not match_end then - return + + if not a then + a = 1 + else + a = tonumber(a) + -- if percentage, then convert to decimal + if percent_sign == "%" then + a = a / 100 + end + -- although alpha doesn't support larger values than 1, css anyways renders it at 1 + if a > 1 then + a = 1 end end - a = tonumber(a) - if not a or a > 1 then - return + + h = tonumber(h) or 1 + -- single turn is 360 + if turn == "turn" then + h = 360 * h end - h = tonumber(h) + + -- if hue angle if greater than 360, then calculate the hue within 360 if h > 360 then - return + local turns = h / 360 + h = 360 * (turns - floor(turns)) end + + -- if saturation or luminance percentage is greater than 100 then reset it to 100 s = tonumber(s) if s > 100 then - return + s = 100 end l = tonumber(l) if l > 100 then - return + l = 100 end + local r, g, b = color.hsl_to_rgb(h / 360, s / 100, l / 100) if r == nil or g == nil or b == nil then return diff --git a/lua/colorizer/matcher.lua b/lua/colorizer/matcher.lua index dba67c0..d55a673 100644 --- a/lua/colorizer/matcher.lua +++ b/lua/colorizer/matcher.lua @@ -17,7 +17,7 @@ local parser = { ["_rgb"] = color.rgb_function_parser, ["_rgba"] = color.rgba_function_parser, ["_hsl"] = color.hsl_function_parser, - ["_hsla"] = color.hsla_function_parser, + ["_hsla"] = color.hsl_function_parser, } local matcher = {} @@ -49,7 +49,7 @@ function matcher.compile(matchers, matchers_trie) if prefix then local fn = "_" .. prefix if parser[fn] then - return parser[fn](line, i, matchers[fn]) + return parser[fn](line, i, matchers[prefix]) end end @@ -145,6 +145,10 @@ function matcher.make(options) table.insert(matchers_prefix, "hsl") end + for _, value in ipairs(matchers_prefix) do + matchers[value] = { prefix = value } + end + loop_parse_fn = matcher.compile(matchers, matchers_prefix) MATCHER_CACHE[matcher_key] = loop_parse_fn -- cgit v1.2.3-70-g09d2