From dde3084106a70b9a79d48f426f6d6fec6fd203f7 Mon Sep 17 00:00:00 2001 From: akianonymus Date: Mon, 27 Feb 2023 14:30:10 +0530 Subject: Separate parsers into individual files --- lua/colorizer/color.lua | 394 +------------------------------------- lua/colorizer/matcher.lua | 23 ++- lua/colorizer/parser/argb_hex.lua | 58 ++++++ lua/colorizer/parser/hsl.lua | 110 +++++++++++ lua/colorizer/parser/names.lua | 84 ++++++++ lua/colorizer/parser/rgb.lua | 114 +++++++++++ lua/colorizer/parser/rgba_hex.lua | 68 +++++++ 7 files changed, 448 insertions(+), 403 deletions(-) create mode 100644 lua/colorizer/parser/argb_hex.lua create mode 100644 lua/colorizer/parser/hsl.lua create mode 100644 lua/colorizer/parser/names.lua create mode 100644 lua/colorizer/parser/rgb.lua create mode 100644 lua/colorizer/parser/rgba_hex.lua (limited to 'lua') diff --git a/lua/colorizer/color.lua b/lua/colorizer/color.lua index 370131b..29af64d 100644 --- a/lua/colorizer/color.lua +++ b/lua/colorizer/color.lua @@ -1,66 +1,7 @@ ----Helper functions to parse different colour formats +---Helper color functions --@module colorizer.color -local api = vim.api - -local bit = require "bit" -local floor, min, max = math.floor, math.min, math.max -local band, rshift, lshift, tohex = bit.band, bit.rshift, bit.lshift, bit.tohex - -local Trie = require "colorizer.trie" - -local utils = require "colorizer.utils" -local byte_is_alphanumeric = utils.byte_is_alphanumeric -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 color = {} -local ARGB_MINIMUM_LENGTH = #"0xAARRGGBB" - 1 ----parse for 0xaarrggbb and return rgb hex. --- a format used in android apps ----@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 hex value ended ----@return string|nil: rgb hex value -function color.argb_hex_parser(line, i) - if #line < i + ARGB_MINIMUM_LENGTH then - return - end - - local j = i + 2 - - local n = j + 8 - local alpha - local v = 0 - while j <= min(n, #line) do - local b = line:byte(j) - if not byte_is_hex(b) then - break - end - if j - i <= 3 then - alpha = parse_hex(b) + lshift(alpha or 0, 4) - else - v = parse_hex(b) + lshift(v, 4) - end - j = j + 1 - end - if #line >= j and byte_is_alphanumeric(line:byte(j)) then - return - end - local length = j - i - if length ~= 10 then - return - end - alpha = tonumber(alpha) / 255 - local r = floor(band(rshift(v, 16), 0xFF) * alpha) - local g = floor(band(rshift(v, 8), 0xFF) * alpha) - local b = floor(band(v, 0xFF) * alpha) - local rgb_hex = string.format("%02x%02x%02x", r, g, b) - return length, rgb_hex -end - --- Converts an HSL color value to RGB. ---@param h number: Hue ---@param s number: Saturation @@ -86,107 +27,6 @@ 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() 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 ----@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, 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*(,?)%s*(%d+)%%(%s?)%s*(,?)%s*(%d+)%%%s*(/?,?)%s*([.%d]*)([%%]?)%s*%)()" - - if opts.prefix == "hsl" then - min_len = CSS_HSL_FN_MINIMUM_LENGTH - end - - if #line < i + min_len then - return - end - - local h, deg, turn, ssep1, csep1, s, ssep2, csep2, l, sep3, a, percent_sign, match_end = line:sub(i):match(pattern) - if not match_end then - return - end - if a == "" then - a = nil - else - min_commas = min_commas + 1 - end - - -- the text after hue should be either deg or empty - if not ((deg == "") or (deg == "deg") or (turn == "turn")) then - return - end - - 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 - - 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 - - h = tonumber(h) or 1 - -- single turn is 360 - if turn == "turn" then - h = 360 * h - end - - -- if hue angle if greater than 360, then calculate the hue within 360 - if h > 360 then - 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 - s = 100 - end - l = tonumber(l) - if l > 100 then - 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 - end - local rgb_hex = string.format("%02x%02x%02x", r * a, g * a, b * a) - return match_end - 1, rgb_hex -end - ---Convert hsl colour values to rgb. -- Source: https://gist.github.com/mjackson/5311256 ---@param p number @@ -229,236 +69,4 @@ function color.is_bright(r, g, b) end end -local COLOR_MAP -local COLOR_TRIE -local COLOR_NAME_MINLEN, COLOR_NAME_MAXLEN -local COLOR_NAME_SETTINGS = { lowercase = true, strip_digits = false } -local TAILWIND_ENABLED = false ---- Grab all the colour values from `vim.api.nvim_get_color_map` and create a lookup table. --- COLOR_MAP is used to store the colour values ----@param line string: Line to parse ----@param i number: Index of line from where to start parsing ----@param opts table: Currently contains whether tailwind is enabled or not -function color.name_parser(line, i, opts) - --- Setup the COLOR_MAP and COLOR_TRIE - if not COLOR_TRIE or opts.tailwind ~= TAILWIND_ENABLED then - COLOR_MAP = {} - COLOR_TRIE = Trie() - for k, v in pairs(api.nvim_get_color_map()) do - if not (COLOR_NAME_SETTINGS.strip_digits and k:match "%d+$") then - COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#k, COLOR_NAME_MINLEN) or #k - COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#k, COLOR_NAME_MAXLEN) or #k - local rgb_hex = tohex(v, 6) - COLOR_MAP[k] = rgb_hex - COLOR_TRIE:insert(k) - if COLOR_NAME_SETTINGS.lowercase then - local lowercase = k:lower() - COLOR_MAP[lowercase] = rgb_hex - COLOR_TRIE:insert(lowercase) - end - end - end - if opts and opts.tailwind then - if opts.tailwind == true or opts.tailwind == "normal" or opts.tailwind == "both" then - local tailwind = require "colorizer.tailwind_colors" - -- setup tailwind colors - for k, v in pairs(tailwind.colors) do - for _, pre in ipairs(tailwind.prefixes) do - local name = pre .. "-" .. k - COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#name, COLOR_NAME_MINLEN) or #name - COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#name, COLOR_NAME_MAXLEN) or #name - COLOR_MAP[name] = v - COLOR_TRIE:insert(name) - end - end - end - end - TAILWIND_ENABLED = opts.tailwind - end - - if #line < i + COLOR_NAME_MINLEN - 1 then - return - end - - if i > 1 and byte_is_valid_colorchar(line:byte(i - 1)) then - return - end - - local prefix = COLOR_TRIE:longest_prefix(line, i) - if prefix then - -- Check if there is a letter here so as to disallow matching here. - -- Take the Blue out of Blueberry - -- Line end or non-letter. - local next_byte_index = i + #prefix - if #line >= next_byte_index and byte_is_valid_colorchar(line:byte(next_byte_index)) then - return - end - return #prefix, COLOR_MAP[prefix] - 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() 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 ----@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, 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 - - if #line < i + min_len then - return - end - - 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 - - if a == "" then - a = nil - else - min_commas = min_commas + 1 - end - - local units = ("%s%s%s"):format(unit1, unit2, unit3) - if units:match "%%" then - if not ((count(units, "%%")) == min_percent) then - return - end - end - - 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 - - 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 = tonumber(g) - if not g then - return - end - 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 - ----parse for #rrggbbaa and return rgb hex. --- a format used in android apps ----@param line string: line to parse ----@param i number: index of line from where to start parsing ----@param opts table: Containing minlen, maxlen, valid_lengths ----@return number|nil: index of line where the hex value ended ----@return string|nil: rgb hex value -function color.rgba_hex_parser(line, i, opts) - local minlen, maxlen, valid_lengths = opts.minlen, opts.maxlen, opts.valid_lengths - local j = i + 1 - if #line < j + minlen - 1 then - return - end - - if i > 1 and byte_is_alphanumeric(line:byte(i - 1)) then - return - end - - local n = j + maxlen - local alpha - local v = 0 - - while j <= min(n, #line) do - local b = line:byte(j) - if not byte_is_hex(b) then - break - end - if j - i >= 7 then - alpha = parse_hex(b) + lshift(alpha or 0, 4) - else - v = parse_hex(b) + lshift(v, 4) - end - j = j + 1 - end - - if #line >= j and byte_is_alphanumeric(line:byte(j)) then - return - end - - local length = j - i - if length ~= 4 and length ~= 7 and length ~= 9 then - return - end - - if alpha then - alpha = tonumber(alpha) / 255 - local r = floor(band(rshift(v, 16), 0xFF) * alpha) - local g = floor(band(rshift(v, 8), 0xFF) * alpha) - local b = floor(band(v, 0xFF) * alpha) - local rgb_hex = string.format("%02x%02x%02x", r, g, b) - return 9, rgb_hex - end - return (valid_lengths[length - 1] and length), line:sub(i + 1, i + length - 1) -end - return color diff --git a/lua/colorizer/matcher.lua b/lua/colorizer/matcher.lua index ccd72a2..1f3eede 100644 --- a/lua/colorizer/matcher.lua +++ b/lua/colorizer/matcher.lua @@ -3,21 +3,24 @@ local Trie = require "colorizer.trie" local min, max = math.min, math.max -local color = require "colorizer.color" -local color_name_parser = color.name_parser -local rgba_hex_parser = color.rgba_hex_parser +local color_name_parser = require "colorizer.parser.names" -local sass = require "colorizer.sass" -local sass_name_parser = sass.name_parser +local rgb_function_parser = require "colorizer.parser.rgb" +local hsl_function_parser = require "colorizer.parser.hsl" + +local argb_hex_parser = require "colorizer.parser.argb_hex" +local rgba_hex_parser = require "colorizer.parser.rgba_hex" + +local sass_name_parser = require("colorizer.sass").name_parser local B_HASH, DOLLAR_HASH = ("#"):byte(), ("$"):byte() local parser = { - ["_0x"] = color.argb_hex_parser, - ["_rgb"] = color.rgb_function_parser, - ["_rgba"] = color.rgb_function_parser, - ["_hsl"] = color.hsl_function_parser, - ["_hsla"] = color.hsl_function_parser, + ["_0x"] = argb_hex_parser, + ["_rgb"] = rgb_function_parser, + ["_rgba"] = rgb_function_parser, + ["_hsl"] = hsl_function_parser, + ["_hsla"] = hsl_function_parser, } local matcher = {} diff --git a/lua/colorizer/parser/argb_hex.lua b/lua/colorizer/parser/argb_hex.lua new file mode 100644 index 0000000..f664dc3 --- /dev/null +++ b/lua/colorizer/parser/argb_hex.lua @@ -0,0 +1,58 @@ +---Helper function to parse argb + +local bit = require "bit" +local floor, min = math.floor, math.min +local band, rshift, lshift = bit.band, bit.rshift, bit.lshift + +local utils = require "colorizer.utils" +local byte_is_alphanumeric = utils.byte_is_alphanumeric +local byte_is_hex = utils.byte_is_hex +local parse_hex = utils.parse_hex + +local parser = {} + +local ARGB_MINIMUM_LENGTH = #"0xAARRGGBB" - 1 +---parse for 0xaarrggbb and return rgb hex. +-- a format used in android apps +---@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 hex value ended +---@return string|nil: rgb hex value +function parser.argb_hex_parser(line, i) + if #line < i + ARGB_MINIMUM_LENGTH then + return + end + + local j = i + 2 + + local n = j + 8 + local alpha + local v = 0 + while j <= min(n, #line) do + local b = line:byte(j) + if not byte_is_hex(b) then + break + end + if j - i <= 3 then + alpha = parse_hex(b) + lshift(alpha or 0, 4) + else + v = parse_hex(b) + lshift(v, 4) + end + j = j + 1 + end + if #line >= j and byte_is_alphanumeric(line:byte(j)) then + return + end + local length = j - i + if length ~= 10 then + return + end + alpha = tonumber(alpha) / 255 + local r = floor(band(rshift(v, 16), 0xFF) * alpha) + local g = floor(band(rshift(v, 8), 0xFF) * alpha) + local b = floor(band(v, 0xFF) * alpha) + local rgb_hex = string.format("%02x%02x%02x", r, g, b) + return length, rgb_hex +end + +return parser.argb_hex_parser diff --git a/lua/colorizer/parser/hsl.lua b/lua/colorizer/parser/hsl.lua new file mode 100644 index 0000000..cb6a19d --- /dev/null +++ b/lua/colorizer/parser/hsl.lua @@ -0,0 +1,110 @@ +---Helper function to parse argb +local count = require("colorizer.utils").count +local floor = math.floor + +local hsl_to_rgb = require("colorizer.color").hsl_to_rgb + +local parser = {} + +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() 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 +---@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 parser.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*(,?)%s*(%d+)%%(%s?)%s*(,?)%s*(%d+)%%%s*(/?,?)%s*([.%d]*)([%%]?)%s*%)()" + + if opts.prefix == "hsl" then + min_len = CSS_HSL_FN_MINIMUM_LENGTH + end + + if #line < i + min_len then + return + end + + local h, deg, turn, ssep1, csep1, s, ssep2, csep2, l, sep3, a, percent_sign, match_end = line:sub(i):match(pattern) + if not match_end then + return + end + if a == "" then + a = nil + else + min_commas = min_commas + 1 + end + + -- the text after hue should be either deg or empty + if not ((deg == "") or (deg == "deg") or (turn == "turn")) then + return + end + + 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 + + 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 + + h = tonumber(h) or 1 + -- single turn is 360 + if turn == "turn" then + h = 360 * h + end + + -- if hue angle if greater than 360, then calculate the hue within 360 + if h > 360 then + 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 + s = 100 + end + l = tonumber(l) + if l > 100 then + l = 100 + end + + local r, g, b = hsl_to_rgb(h / 360, s / 100, l / 100) + if r == nil or g == nil or b == nil then + return + end + local rgb_hex = string.format("%02x%02x%02x", r * a, g * a, b * a) + return match_end - 1, rgb_hex +end + +return parser.hsl_function_parser diff --git a/lua/colorizer/parser/names.lua b/lua/colorizer/parser/names.lua new file mode 100644 index 0000000..6fc7043 --- /dev/null +++ b/lua/colorizer/parser/names.lua @@ -0,0 +1,84 @@ +---Helper function to parse argb +local api = vim.api + +local bit = require "bit" +local tohex = bit.tohex + +local min, max = math.min, math.max + +local Trie = require "colorizer.trie" + +local utils = require "colorizer.utils" +local byte_is_valid_colorchar = utils.byte_is_valid_colorchar + +local parser = {} + +local COLOR_MAP +local COLOR_TRIE +local COLOR_NAME_MINLEN, COLOR_NAME_MAXLEN +local COLOR_NAME_SETTINGS = { lowercase = true, strip_digits = false } +local TAILWIND_ENABLED = false +--- Grab all the colour values from `vim.api.nvim_get_color_map` and create a lookup table. +-- COLOR_MAP is used to store the colour values +---@param line string: Line to parse +---@param i number: Index of line from where to start parsing +---@param opts table: Currently contains whether tailwind is enabled or not +function parser.name_parser(line, i, opts) + --- Setup the COLOR_MAP and COLOR_TRIE + if not COLOR_TRIE or opts.tailwind ~= TAILWIND_ENABLED then + COLOR_MAP = {} + COLOR_TRIE = Trie() + for k, v in pairs(api.nvim_get_color_map()) do + if not (COLOR_NAME_SETTINGS.strip_digits and k:match "%d+$") then + COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#k, COLOR_NAME_MINLEN) or #k + COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#k, COLOR_NAME_MAXLEN) or #k + local rgb_hex = tohex(v, 6) + COLOR_MAP[k] = rgb_hex + COLOR_TRIE:insert(k) + if COLOR_NAME_SETTINGS.lowercase then + local lowercase = k:lower() + COLOR_MAP[lowercase] = rgb_hex + COLOR_TRIE:insert(lowercase) + end + end + end + if opts and opts.tailwind then + if opts.tailwind == true or opts.tailwind == "normal" or opts.tailwind == "both" then + local tailwind = require "colorizer.tailwind_colors" + -- setup tailwind colors + for k, v in pairs(tailwind.colors) do + for _, pre in ipairs(tailwind.prefixes) do + local name = pre .. "-" .. k + COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#name, COLOR_NAME_MINLEN) or #name + COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#name, COLOR_NAME_MAXLEN) or #name + COLOR_MAP[name] = v + COLOR_TRIE:insert(name) + end + end + end + end + TAILWIND_ENABLED = opts.tailwind + end + + if #line < i + COLOR_NAME_MINLEN - 1 then + return + end + + if i > 1 and byte_is_valid_colorchar(line:byte(i - 1)) then + return + end + + local prefix = COLOR_TRIE:longest_prefix(line, i) + if prefix then + -- Check if there is a letter here so as to disallow matching here. + -- Take the Blue out of Blueberry + -- Line end or non-letter. + local next_byte_index = i + #prefix + if #line >= next_byte_index and byte_is_valid_colorchar(line:byte(next_byte_index)) then + return + end + return #prefix, COLOR_MAP[prefix] + end +end + +return parser.name_parser diff --git a/lua/colorizer/parser/rgb.lua b/lua/colorizer/parser/rgb.lua new file mode 100644 index 0000000..2786a60 --- /dev/null +++ b/lua/colorizer/parser/rgb.lua @@ -0,0 +1,114 @@ +---Helper function to parse argb +local count = require("colorizer.utils").count + +local parser = {} +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() 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 +---@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 parser.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 + + if #line < i + min_len then + return + end + + 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 + + if a == "" then + a = nil + else + min_commas = min_commas + 1 + end + + local units = ("%s%s%s"):format(unit1, unit2, unit3) + if units:match "%%" then + if not ((count(units, "%%")) == min_percent) then + return + end + end + + 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 + + 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 = tonumber(g) + if not g then + return + end + 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 + +return parser.rgb_function_parser diff --git a/lua/colorizer/parser/rgba_hex.lua b/lua/colorizer/parser/rgba_hex.lua new file mode 100644 index 0000000..0f16fbe --- /dev/null +++ b/lua/colorizer/parser/rgba_hex.lua @@ -0,0 +1,68 @@ +---Helper function to parse argb +local bit = require "bit" +local floor, min = math.floor, math.min +local band, rshift, lshift = bit.band, bit.rshift, bit.lshift + +local utils = require "colorizer.utils" +local byte_is_alphanumeric = utils.byte_is_alphanumeric +local byte_is_hex = utils.byte_is_hex +local parse_hex = utils.parse_hex + +local parser = {} + +---parse for #rrggbbaa and return rgb hex. +-- a format used in android apps +---@param line string: line to parse +---@param i number: index of line from where to start parsing +---@param opts table: Containing minlen, maxlen, valid_lengths +---@return number|nil: index of line where the hex value ended +---@return string|nil: rgb hex value +function parser.rgba_hex_parser(line, i, opts) + local minlen, maxlen, valid_lengths = opts.minlen, opts.maxlen, opts.valid_lengths + local j = i + 1 + if #line < j + minlen - 1 then + return + end + + if i > 1 and byte_is_alphanumeric(line:byte(i - 1)) then + return + end + + local n = j + maxlen + local alpha + local v = 0 + + while j <= min(n, #line) do + local b = line:byte(j) + if not byte_is_hex(b) then + break + end + if j - i >= 7 then + alpha = parse_hex(b) + lshift(alpha or 0, 4) + else + v = parse_hex(b) + lshift(v, 4) + end + j = j + 1 + end + + if #line >= j and byte_is_alphanumeric(line:byte(j)) then + return + end + + local length = j - i + if length ~= 4 and length ~= 7 and length ~= 9 then + return + end + + if alpha then + alpha = tonumber(alpha) / 255 + local r = floor(band(rshift(v, 16), 0xFF) * alpha) + local g = floor(band(rshift(v, 8), 0xFF) * alpha) + local b = floor(band(v, 0xFF) * alpha) + local rgb_hex = string.format("%02x%02x%02x", r, g, b) + return 9, rgb_hex + end + return (valid_lengths[length - 1] and length), line:sub(i + 1, i + length - 1) +end + +return parser.rgba_hex_parser -- cgit v1.2.3-70-g09d2