aboutsummaryrefslogtreecommitdiff
path: root/lua/colorizer/parser/hsl.lua
blob: cb6a19d1c065fed5db5dc145ff7928ff3b3f0dbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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