From ebb03999a8cffcd927e36d54062d9d01e868992b Mon Sep 17 00:00:00 2001 From: akianonymus Date: Sun, 26 Feb 2023 23:12:48 +0530 Subject: feat: Support modern rgb/rgba syntax https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb --- lua/colorizer/color.lua | 126 +++++++++++++++++++++++++++++----------------- lua/colorizer/matcher.lua | 2 +- lua/colorizer/utils.lua | 17 +------ test/expectation.txt | 7 +-- 4 files changed, 86 insertions(+), 66 deletions(-) diff --git a/lua/colorizer/color.lua b/lua/colorizer/color.lua index fe3e15b..370131b 100644 --- a/lua/colorizer/color.lua +++ b/lua/colorizer/color.lua @@ -14,7 +14,6 @@ local byte_is_hex = utils.byte_is_hex local byte_is_valid_colorchar = utils.byte_is_valid_colorchar local count = utils.count local parse_hex = utils.parse_hex -local percent_or_hex = utils.percent_or_hex local color = {} @@ -298,76 +297,111 @@ function color.name_parser(line, i, opts) end end +local CSS_RGBA_FN_MINIMUM_LENGTH = #"rgba(0,0,0)" - 1 local CSS_RGB_FN_MINIMUM_LENGTH = #"rgb(0,0,0)" - 1 ----Parse for rgb() css function and return rgb hex. +---Parse for rgb() rgba() css function and return rgb hex. +-- For more info: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb ---@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 rgb function ended +---@param opts table: Values passed from matchers like prefix +---@return number|nil: Index of line where the rgb/rgba function ended ---@return string|nil: rgb hex value -function color.rgb_function_parser(line, i) - if #line < i + CSS_RGB_FN_MINIMUM_LENGTH then - return - end - local r, g, b, match_end = line:sub(i):match "^rgb%(%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*(%d+%%?)%s*%)()" - if not match_end then - r, g, b, match_end = line:sub(i):match "^rgb%(%s*(%d+%%?)%s+(%d+%%?)%s+(%d+%%?)%s*%)()" - if not match_end then - return - end - end - r = percent_or_hex(r) - if not r then - return +function color.rgb_function_parser(line, i, opts) + local min_len = CSS_RGBA_FN_MINIMUM_LENGTH + local min_commas, min_spaces, min_percent = 2, 2, 3 + local pattern = "^" + .. opts.prefix + .. "%(%s*([.%d]+)([%%]?)(%s?)%s*(,?)%s*([.%d]+)([%%]?)(%s?)%s*(,?)%s*([.%d]+)([%%]?)%s*(/?,?)%s*([.%d]*)([%%]?)%s*%)()" + + if opts.prefix == "rgb" then + min_len = CSS_RGB_FN_MINIMUM_LENGTH end - g = percent_or_hex(g) - if not g then + + if #line < i + min_len then return end - b = percent_or_hex(b) - if not b then + + local r, unit1, ssep1, csep1, g, unit2, ssep2, csep2, b, unit3, sep3, a, unit_a, match_end = + line:sub(i):match(pattern) + if not match_end then return end - local rgb_hex = string.format("%02x%02x%02x", r, g, b) - return match_end - 1, rgb_hex -end -local CSS_RGBA_FN_MINIMUM_LENGTH = #"rgba(0,0,0,0)" - 1 ----Parse for rgba() css function and return rgb hex. --- Todo consider removing the regexes here --- Todo this might not be the best approach to alpha channel. --- Things like pumblend might be useful here. ----@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 rgba function ended ----@return string|nil: rgb hex value -function color.rgba_function_parser(line, i) - if #line < i + CSS_RGBA_FN_MINIMUM_LENGTH then - return + if a == "" then + a = nil + else + min_commas = min_commas + 1 end - local r, g, b, a, match_end = - line:sub(i):match "^rgba%(%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*([.%d]+)%s*%)()" - if not match_end then - r, g, b, a, match_end = line:sub(i):match "^rgba%(%s*(%d+%%?)%s+(%d+%%?)%s+(%d+%%?)%s+([.%d]+)%s*%)()" - if not match_end then + + local units = ("%s%s%s"):format(unit1, unit2, unit3) + if units:match "%%" then + if not ((count(units, "%%")) == min_percent) then return end end - a = tonumber(a) - if not a or a > 1 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 (count(c_seps, ",") == min_commas) then + return + end + -- space separator syntax with decimal or percentage alpha + elseif count(s_seps, "%s") >= min_spaces then + if a then + if not (c_seps == "/") then + return + end + end + else return end - r = percent_or_hex(r) + + if not a then + a = 1 + else + a = tonumber(a) + -- if percentage, then convert to decimal + if unit_a == "%" 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 + + r = tonumber(r) if not r then return end - g = percent_or_hex(g) + g = tonumber(g) if not g then return end - b = percent_or_hex(b) + b = tonumber(b) if not b then return end + + if unit1 == "%" then + r = r / 100 * 255 + g = g / 100 * 255 + b = b / 100 * 255 + else + -- although r,g,b doesn't support larger values than 255, css anyways renders it at 255 + if r > 255 then + r = 255 + end + if g > 255 then + g = 255 + end + if b > 255 then + b = 255 + end + end + local rgb_hex = string.format("%02x%02x%02x", r * a, g * a, b * a) return match_end - 1, rgb_hex end diff --git a/lua/colorizer/matcher.lua b/lua/colorizer/matcher.lua index 21d7b4f..ccd72a2 100644 --- a/lua/colorizer/matcher.lua +++ b/lua/colorizer/matcher.lua @@ -15,7 +15,7 @@ local B_HASH, DOLLAR_HASH = ("#"):byte(), ("$"):byte() local parser = { ["_0x"] = color.argb_hex_parser, ["_rgb"] = color.rgb_function_parser, - ["_rgba"] = color.rgba_function_parser, + ["_rgba"] = color.rgb_function_parser, ["_hsl"] = color.hsl_function_parser, ["_hsla"] = color.hsl_function_parser, } diff --git a/lua/colorizer/utils.lua b/lua/colorizer/utils.lua index 724b46e..ae590cd 100644 --- a/lua/colorizer/utils.lua +++ b/lua/colorizer/utils.lua @@ -122,25 +122,10 @@ function utils.parse_hex(byte) return rshift(BYTE_CATEGORY[byte], 4) end -local b_percent = string.byte "%" ---- Obvious. ----@param v string ----@return number|nil -function utils.percent_or_hex(v) - if v:byte(-1) == b_percent then - return tonumber(v:sub(1, -2)) / 100 * 255 - end - local x = tonumber(v) - if x > 255 then - return - end - return x -end - --- Watch a file for changes and execute callback ---@param path string: File path ---@param callback function: Callback to execute ----@param ... array: params for callback +---@param ... table: params for callback ---@return function|nil function utils.watch_file(path, callback, ...) if not path or type(callback) ~= "function" then diff --git a/test/expectation.txt b/test/expectation.txt index e3cc609..6150778 100644 --- a/test/expectation.txt +++ b/test/expectation.txt @@ -18,8 +18,10 @@ White #def #deadbeef -rgb(0,0,0) rgb(10, 100 , 100) -rgba(200,30,0,1) rgba(200,30,0,0.5) +rgb( 31 12.90 50 /0.5) rgb( 10, 100 , 100, 0.3) +rgb(30% 20% 50%) rgb(0,0,0) rgb(255 122 127 / 80%) +rgb(255 122 127 / .2) rgba(200,30,0,1) rgba(200,30,0,0.5) + hsl(300 50% 50%) hsl(300 50% 50% / 1) hsl(100 80% 50% / 0.4) hsl(990 80% 50% / 0.4) hsl(720 80% 50% / 0.4) hsl(1turn 80% 50% / 0.4) hsl(0.4turn 80% 50% / 0.4) hsl(1.4turn 80% 50% / 0.4) @@ -38,7 +40,6 @@ Blueberry Gray1000 BlueGree BlueGray #define #def0 matcher#add -rgb(10,256,100) rgb (10,255,100) rgb(10, 1 00 , 100) hsl(300 50% 50% 1) -- cgit v1.2.3-70-g09d2