@@ -11,17 +11,56 @@ if ! ( v:version >= 700 && has('syntax') && ( has('gui_running') || has('nvim')
1111 finish
1212endif
1313
14+ let s: _invalid = -9999 " used after bounding/wrapping, so this is safe
15+
16+ function ! s: bound (n , min , max )
17+ let n = a: n
18+ if n < a: min | let n = a: min
19+ elseif n > a: max | let n = a: max | endif
20+ return str2nr ( string (n + 0.5 ) )
21+ endfunction
22+
23+ function ! s: rgb2hex (r , g , b )
24+ let [r ,g ,b ] = map ( [a: r ,a: g ,a: b ], ' s:bound(v:val, 0, 255)' )
25+ return printf ( ' %02x%02x%02x' , r , g , b )
26+ endfunction
27+
1428function ! s: rgb2color (r ,g ,b )
15- " Convert 80% -> 204, 100% -> 255, etc.
16- let rgb = map ( [a: r ,a: g ,a: b ], ' v:val =~ "%$" ? ( 255 * v:val ) / 100 : v:val' )
17- return printf ( ' %02x%02x%02x ' , rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] )
29+ " Convert 35.6% -> 90.78, 80% -> 204, 100% -> 255, etc.
30+ let [ r , g , b ] = map ( [a: r ,a: g ,a: b ], ' v:val =~ "%$" ? str2float( v:val) * 2.55 : str2float( v:val) ' )
31+ return s: rgb2hex ( r , g , b )
1832endfunction
1933
20- function ! s: hsl2color (h ,s ,l )
21- " Convert 80% -> 0.8, 100% -> 1.0, etc.
22- let [s ,l ] = map ( [a: s , a: l ], ' v:val =~ "%$" ? v:val / 100.0 : v:val + 0.0' )
34+ " Vimscript modulo can't handle decimals. Fix that.
35+ function ! s: modulo (a , b )
36+ let num = str2nr ( string (a: a ) )
37+ let extra = a: a - num
38+ return str2nr (a: a - extra) % a: b + extra
39+ endfunction
40+
41+ function ! s: angle2deg (angle, units)
42+ let deg = str2float (a: angle )
43+ " angles: https://developer.mozilla.org/en-US/docs/Web/CSS/angle
44+ if a: units == " rad"
45+ let deg = deg * 57.29577951308232 " deg = rad * 180 / pi, 360deg == 6.2832rad
46+ elseif a: units == " grad"
47+ let deg = deg * 0.9 " 360deg = 400grad
48+ elseif a: units == " turn"
49+ let deg = deg * 360.0 " 360deg = 1turn
50+ elseif a: units != " " && a: units != " deg"
51+ return s: _invalid " percent is invalid here
52+ endif
53+ let deg = s: modulo (deg, 360 )
54+ if deg < 0 | let deg = deg + 360 | endif
55+ return deg
56+ endfunction
57+
58+ function ! s: hsl2rgb (h ,u ,s ,l )
59+ let [s ,l ] = map ( [a: s , a: l ], ' str2nr(v:val) >= 1 ? 1.0 : str2float(v:val)' )
2360 " algorithm transcoded to vim from http://www.w3.org/TR/css3-color/#hsl-color
24- let hh = ( a: h % 360 ) / 360.0
61+ let hh = s: angle2deg (a: h , a: u )
62+ if hh == s: _invalid | return [hh ,hh ,hh ] | endif
63+ let hh = hh / 360.0
2564 let m2 = l <= 0.5 ? l * ( s + 1 ) : l + s - l * s
2665 let m1 = l * 2 - m2
2766 let rgb = []
@@ -32,12 +71,48 @@ function! s:hsl2color(h,s,l)
3271 \ h * 2 < 1 ? m2 :
3372 \ h * 3 < 2 ? m1 + ( m2 - m1 ) * ( 2 /3.0 - h ) * 6 :
3473 \ m1
35- if v > 1.0 | return ' ' | endif
74+ if v > 1.0 | return [ s: _invalid , s: _invalid , s: _invalid ] | endif
3675 let rgb += [ float2nr ( 255 * v ) ]
3776 endfor
38- return printf ( ' %02x%02x%02x ' , rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] )
77+ return rgb
3978endfunction
4079
80+ function ! s: pctvet (h ,s ,l )
81+ " hue cannot be a percent, the others must be percents. same with hwb
82+ if a: h = ~ " %$" || a: s !~ " %$" || a: l !~ " %$"
83+ return [s: _invalid , s: _invalid , s: _invalid ]
84+ endif
85+ let [s ,l ] = map ( [a: s , a: l ], ' v:val >= 100 ? 1.0 : v:val / 100.0' )
86+ return [a: h , s , l ]
87+ endfunction
88+
89+ function ! s: hsl2color (h ,u ,s ,l )
90+ let [h ,s ,l ] = s: pctvet (a: h , a: s , a: l ) " convert saturation & luminance % -> num
91+ if h == s: _invalid | return ' ' | endif
92+ let [r ,g ,b ] = s: hsl2rgb (h , a: u , s , l )
93+ if r == s: _invalid | return ' ' | endif
94+ return s: rgb2hex (r , g , b )
95+ endfunction
96+
97+ function ! s: hwb2color (h ,u ,w ,b )
98+ let [h ,w ,b ] = s: pctvet (a: h , a: w , a: b ) " convert whiteness & blackness % -> num
99+ if h == s: _invalid | return ' ' | endif
100+ " algorithm transcoded to vim from https://drafts.csswg.org/css-color/#hwb-to-rgb
101+ if w + b >= 1
102+ let gray = w / (w + b )
103+ let [r ,g ,b ] = [gray ,gray ,gray ]
104+ else
105+ let [r ,g ,b ] = map ( s: hsl2rgb (a: h , a: u , 1.0 , 0.5 ), ' v:val * (1 - w - b) + w' )
106+ endif
107+ return s: rgb2hex (r , g , b )
108+ endfunction
109+
110+ " TODO (probably not): new "device-independent colors":
111+ " lch(), oklch(), lab(), oklab(), and color().
112+ " These look hard and I don't see reference algorithms to convert to sRGB.
113+ " https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
114+ " https://drafts.csswg.org/css-color/#lab-colors
115+
41116let s: _1_3 = 1.0 /3
42117let s: _16_116 = 16.0 /116.0
43118let s: cos16 = cos (16 * (180 /atan2 (0 ,-1 )))
@@ -170,14 +245,17 @@ function! s:create_syn_match()
170245 let hex = submatch (1 )
171246 let funcname = submatch (2 )
172247
248+ if funcname = ~ ' rgb' && submatch (4 ) != ' ' | return ' ' | endif " rgb() doesn't support units
249+
173250 let rgb_color
174- \ = funcname == ' rgb' ? s: rgb2color (submatch (3 ),submatch (4 ),submatch (5 ))
175- \ : funcname == ' hsl' ? s: hsl2color (submatch (3 ),submatch (4 ),submatch (5 ))
251+ \ = funcname = ~ ' ^rgb' ? s: rgb2color (submatch (3 ),submatch (5 ),submatch (6 ))
252+ \ : funcname = ~ ' ^hsl' ? s: hsl2color (submatch (3 ),submatch (4 ),submatch (5 ),submatch (6 ))
253+ \ : funcname == ' hwb' ? s: hwb2color (submatch (3 ),submatch (4 ),submatch (5 ),submatch (6 ))
176254 \ : strlen (hex) >= 6 ? tolower (hex[0 :5 ])
177255 \ : strlen (hex) >= 3 ? tolower (hex[0 ].hex[0 ].hex[1 ].hex[1 ].hex[2 ].hex[2 ])
178256 \ : ' '
179257
180- if rgb_color == ' ' | throw ' css_color: create_syn_match invoked on bad match data ' | endif
258+ if rgb_color == ' ' | return ' ' | endif
181259 let s: pattern_color [pattern] = rgb_color
182260 endif
183261
@@ -229,14 +307,14 @@ endfunction
229307
230308let s: _hexcolor = ' #\(\x\{3}\%(\>\|\x\{3}\>\)\)' " submatch 1
231309let s: _rgbacolor = ' #\(\x\{3}\%(\>\|\x\%(\>\|\x\{2}\%(\>\|\x\{2}\>\)\)\)\)' " submatch 1
232- let s: _funcname = ' \(rgb\|hsl\) a\?' " submatch 2
233- let s: _funcprep = ' \%(\s*\S \+\%(\ %(\s \+\S\+\)\{2}\ |\%(\s*,\s*\S\ +\)\{2}\) \)\@=' " lookahead for 12,34,56 vs 12 34 56 to avoid 12 34,56
310+ let s: _funcname = ' \(rgb\a\?\|hsla\?\|hwb\) ' " submatch 2
311+ let s: _consistent = ' \%(\%(\s\+[0-9.%] \+\)\{2}\s*\ %(\/\s*[-0-9.%] \+\)\?[)]\ |\%(\s*,\s*[-0-9.%]\ +\)\{2,3}[)] \)\@=' " lookahead: 1 2 3 or 1 2 3/4 or 1,2,3 or 1,2,3,4 after 1
234312let s: _ws_ = ' \s*'
235- " let s:_numval = '\(\ d\{1,3}\%(\.\d*\)\?%\?\)' " submatch 3,4,5 (use this after implementing ceilings for the output of b:css_color_pat)
236- let s: _numval = ' \(\d\{1,2}\%(\.\d*\)\?%\|100%\|255\|\%(1\?\d\{1,2}\|2[0-4]\d\|25[0-5]\)\%(\.\d*\)\?\) ' " submatch 3,4,5
313+ let s: _numval = ' \(-\?\ d\{1,3}\%(\.\d*\)\?%\?\)' " submatch 3,5,6
314+ let s: _units = ' \(deg\|g\?rad\|turn\)\? ' " submatch 4
237315let s: _listsep = s: _ws_ . ' [,[:space:]]\+'
238316let s: _otherargs_ = s: _ws_ . ' \%([,\/][^)]*\)\?' " ignore alpha
239- let s: _funcexpr = s: _funcname . ' [(]' . s: _funcprep . s: _ws_ . s: _numval . s: _listsep . s: _numval . s: _listsep . s: _numval . s: _otherargs_ . ' [)]'
317+ let s: _funcexpr = s: _funcname . ' [(]' . s: _ws_ . s: _numval . s: _units . s: _consistent . s: _listsep . s: _numval . s: _listsep . s: _numval . s: _otherargs_ . ' [)]'
240318let s: _csscolor = s: _rgbacolor . ' \|' . s: _funcexpr
241319" N.B. sloppy heuristic constants for performance reasons:
242320" a) start somewhere left of screen in case of partially visible colorref
0 commit comments