From 261d23eb2da7d8c794e2c42e11adeaab22ef00e3 Mon Sep 17 00:00:00 2001 From: Sindre Aarsaether Date: Tue, 7 May 2024 06:18:38 +0200 Subject: [PATCH] #color fixes --- packages/imba/src/compiler/colord.imba | 11 ++ packages/imba/src/compiler/constants.imba1 | 2 + packages/imba/src/compiler/grammar.imba1 | 4 +- packages/imba/src/compiler/lexer.imba1 | 4 +- packages/imba/src/compiler/nodes.imba1 | 129 ++++++++------------ packages/imba/src/compiler/styler.imba | 26 ++-- packages/imba/test/apps/style/colormix.imba | 8 +- 7 files changed, 86 insertions(+), 98 deletions(-) create mode 100644 packages/imba/src/compiler/colord.imba diff --git a/packages/imba/src/compiler/colord.imba b/packages/imba/src/compiler/colord.imba new file mode 100644 index 000000000..2ab86c225 --- /dev/null +++ b/packages/imba/src/compiler/colord.imba @@ -0,0 +1,11 @@ +import * as cd from "colord" +import lchPlugin from "colord/plugins/lch" +cd.extend([lchPlugin]) + +export const colord = cd.colord + +export def toLchArray str + let res = cd.colord(str).toLch() + return [res.l,res.c,res.h,res.a] + + diff --git a/packages/imba/src/compiler/constants.imba1 b/packages/imba/src/compiler/constants.imba1 index 2825b842f..afd8ff045 100644 --- a/packages/imba/src/compiler/constants.imba1 +++ b/packages/imba/src/compiler/constants.imba1 @@ -89,6 +89,8 @@ export var OPERATOR_ALIASES = export var HEREGEX_OMIT = /\s+(?:#.*)?/g export var HEREGEX = /// ^ /{3} ([\s\S]+?) /{3} ([a-z]{0,8}) (?!\w) /// +export var HEX_REGEX = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}([A-Fa-f0-9]{2})?)$/ + export var TAG_GLOBAL_ATTRIBUTES = { itemid: 1 itemprop: 1 diff --git a/packages/imba/src/compiler/grammar.imba1 b/packages/imba/src/compiler/grammar.imba1 index 424125742..8d4615b0c 100644 --- a/packages/imba/src/compiler/grammar.imba1 +++ b/packages/imba/src/compiler/grammar.imba1 @@ -488,7 +488,7 @@ var grammar = StyleColor: [ o 'COLOR' do StyleColor.new(A1) - o 'COLORMIX ( StyleFunctionArgs )' do StyleColorMix.new(A1,A3).set(params: A3) + o 'COLORMIX ( StyleFunctionArgs )' do StyleColorMix.new(A1,A3.setEnds(A2,A4)).set(params: A3).setEnds(A1,A4) ] StyleTerm: [ @@ -502,7 +502,7 @@ var grammar = o 'String' do A1 o 'StyleTermPlaceholder' do A1 o 'CSSURL' do StyleURL.new(A1) - o 'CSSFUNCTION ( StyleFunctionArgs )' do StyleFunction.new(A1,A3) + o 'CSSFUNCTION ( StyleFunctionArgs )' do StyleFunction.new(A1,A3.setEnds(A2,A4)).setEnds(A1,A4) o 'CSSIDENTIFIER' do StyleIdentifier.new(A1) o 'COMPARE StyleTerm' do A2.set(op: A1) ] diff --git a/packages/imba/src/compiler/lexer.imba1 b/packages/imba/src/compiler/lexer.imba1 index b336b504a..eb4e535f5 100644 --- a/packages/imba/src/compiler/lexer.imba1 +++ b/packages/imba/src/compiler/lexer.imba1 @@ -175,8 +175,8 @@ var NUMBER = /// ^ 0x[\da-f_]+ | # hex ^ 0b[01_]+ | # binary ^ 0o[\d_]+ | # binary - ^ \-?(?:\d[_\d]*)\.?\d[_\d]* (?:e[+-]?\d+)? | # decimal2 - ^ \-?\d*\.?\d+ (?:e[+-]?\d+)? # decimal + ^ [\-]?(?:\d[_\d]*)\.?\d[_\d]* (?:e[+-]?\d+)? | # decimal2 + ^ [\-]?\d*\.?\d+ (?:e[+-]?\d+)? # decimal ///i diff --git a/packages/imba/src/compiler/nodes.imba1 b/packages/imba/src/compiler/nodes.imba1 index 609a0fa44..3dd0e92c5 100644 --- a/packages/imba/src/compiler/nodes.imba1 +++ b/packages/imba/src/compiler/nodes.imba1 @@ -6,6 +6,7 @@ var helpers = require './helpers' var constants = require './constants' var fspath = require 'path' import {conv} from '../../vendor/colors' +import {colord} from './colord' import ImbaParseError,ImbaTraverseError from './errors' import Token from './token' @@ -1756,7 +1757,7 @@ export class ListNode < Node def initialize list setup - @nodes = load(list or []) + @nodes = load(list == null ? [] : list) @indentation = null # PERF acces @nodes directly? @@ -12006,12 +12007,11 @@ export class StyleDeclaration < StyleNode self def clone name, params - params = @expr.clone unless params + params = @expr.clone if params == null if typeof params == 'string' or typeof params == 'number' params = [params] if !(params isa Array) and (!(params isa ListNode) or params isa StyleOperation) params = [params] - StyleDeclaration.new(@property.clone(name),params) def visit stack, o @@ -12052,7 +12052,6 @@ export class StyleDeclaration < StyleNode if res isa Array @expr = StyleExpressions.new(res) elif res isa Object - # console.log 'theme has method',method,res for own k,v of res if k.indexOf('&') >= 0 let body = StyleBody.new([]) @@ -12108,15 +12107,19 @@ export class StyleProperty < StyleNode @token = token let raw = String(@token) - if raw[0] == '#' - @kind = 'color' # also split @parts = raw.replace(/(^|\b)\$/g,'--').split(/\b(?=[\^\.\@\!])/g) # .split(/[\.\@]/g) for part,i in @parts @parts[i] = part.replace(/^\.(?=[^\.])/,'@.') - @name = String(@parts[0]) + @name = String(@parts[0]) + + if raw[0] == '#' + @kind = 'color' + if constants.HEX_REGEX.test(@name) + error("Color name {@name} cannot be identical to valid hex color",loc: token[0] or token) + if let m = @name.match(/^(\d+)([a-zA-Z]+)$/) @number = parseInt(m[1]) @@ -12382,31 +12385,24 @@ export class StyleFunction < Node @lcha.push(part) unless name == 'lch' - let nums = [] let alpha = @lcha[3] let kind = name.slice(0,3) - let fmt = { - rgb: [2.55,2.55,2.55] - hsl: [1,1,1] - }[kind] or [0.01,0.01,0.01] - for part,i in @lcha - unless part isa StyleDimension - unless i > 2 # alpha channel can be dynamic - error("Dynamic part not allowed in non-lch #color definitions", loc: part) - - if i < 3 - nums.push(part.toFloat(fmt[i])) + if !(part isa StyleDimension) and i < 3 + return error("Dynamic part not allowed in non-lch #color definitions", loc: part) - let converter = conv[name] - - unless converter and converter:lch - error("Cannot convert {name} to lch", loc: @name) - - @lcha = converter.lch(nums) - @lcha.push(alpha) if alpha + try + let inside = @params.c + if alpha and !(alpha isa StyleDimension) + inside = inside.replace(alpha.c,'1') + let full = "{name}({inside})" + let col = colord(full).toLch() + @lcha = [col:l,col:c,col:h,col:a] + @lcha[3] = alpha if alpha + catch e + error("Failed to parse color", loc: self) self def lcha @@ -12445,25 +12441,20 @@ export class StyleIdentifier < StyleTerm def visit stack let raw = toString - - super - + if raw.match(/^[lcha]$/) + super let mix = stack.up(StyleColorMix) @colormix = mix @resolvedValue = "var(--u_{@colormix.@name}{raw.toUpperCase()})" - - elif raw.match(/^([a-zA-Z]+\d+|black|white)$/) - color = "{raw}" - if self:param - color += "/" + self:param.toAlpha - - + else + if raw.match(/^([a-zA-Z]+\d+|black|white)$/) + color = "{raw}" + if self:param + color += "/" + self:param.toAlpha + super def c o - if @resolvedValue - return @resolvedValue - if @colormix return "var(--u_{@colormix.@name}{toString.toUpperCase()})" @@ -12487,37 +12478,26 @@ export class StyleColor < StyleTerm def visit super - let name = @name = toRaw.slice(1) - - if true # @property and @property.isColor - # only needed for the property color? - if let m = name.match(/^([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}([A-Fa-f0-9]{2})?)$/) - let a = m[2] ? (parseInt(m[2],16) / 255) : 1 - let lch = conv:hex.lch(name.slice(0,6)) - - # Rounding conversion to nearest .1 - for num,i in lch - lch[i] = Math.floor(num * 10) / 10 - - if name[0].match(/\d/) - @lcha = lch - @lcha.push(a) - else - # Cannot know for sure that a color name is not really a hex color - @lcha = [ - "var(--u_{name}L,{lch[0]})" - "var(--u_{name}C,{lch[1]})" - "var(--u_{name}H,{lch[2]})" - "var(--u_{name}A,{a})" - ] + let raw = toRaw + let name = @name = raw.slice(1) - else - @lcha = [ - "var(--u_{name}L)" - "var(--u_{name}C)" - "var(--u_{name}H)" - "var(--u_{name}A,1)" - ] + # only needed for the property color? + if let m = raw.match(constants.HEX_REGEX) + @hex = yes + let col = colord(raw).toLch() + @lcha = [col:l,col:c,col:h,col:a] + else + @lcha = [ + "var(--u_{name}L)" + "var(--u_{name}C)" + "var(--u_{name}H)" + "var(--u_{name}A,1)" + ] + + let a = self:param and self:param.toAlpha + if a != null + a = "var(--{a.slice(1)},100%)" if a[0] == '$' + @lcha[3] = a def lcha @lcha @@ -12526,17 +12506,16 @@ export class StyleColor < StyleTerm let raw = toRaw let name = raw.slice(1) let rich = Color.from(raw) - let a = self:param ? self:param.toAlpha : "var(--u_{name}A,1)" - - if a[0] == '$' - a = "var(--{a.slice(1)},100%)" if @property and @property.isColor + console.log 'deprecated' return rich.toVar - return "lch(var(--u_{name}L) var(--u_{name}C) var(--u_{name}H) / {a})" + let [l,c,h,a] = @lcha + if @hex and a == 1 + return raw - return "hsla2({rich.toVar},{a})" + return "lch({l} {c} {h} / {a})" export class StyleColorMix < StyleTerm diff --git a/packages/imba/src/compiler/styler.imba b/packages/imba/src/compiler/styler.imba index 60f25113f..6a78f3471 100644 --- a/packages/imba/src/compiler/styler.imba +++ b/packages/imba/src/compiler/styler.imba @@ -2,6 +2,7 @@ # var conv = require('../../vendor/colors') import * as selparser from './selparse' import {conv} from '../../vendor/colors' +import {colord,toLchArray} from './colord.imba' import {fonts,colors,variants,named_colors} from './theme.imba' import * as theme from './theme.imba' @@ -396,6 +397,9 @@ export def parseColorString str, to = 'hsl' if named_colors[str] str = named_colors[str] + if to == 'lch' + return toLchArray(str) + if str[0] == '#' let hex = conv.hex.rgb(str) return conv.rgb.hsl(hex) @@ -404,19 +408,14 @@ export def parseColorString str, to = 'hsl' let [a,b,c,d = ''] = m[2].replace(/[\,\/]/g,' ').split(/\s+/g) let out - - console.log 'parsing',str,a,b,c,m,m[2].replace(/[,\/]/g,' ') - - let parse - a=parseColorPart(a) b=parseColorPart(b) c=parseColorPart(c) if to == 'lch' + return conv.rgb.lch([a,b,c]) - if m[1] == 'rgb' or m[1] == 'rgba' out = conv.rgb.hsl([parseFloat(a),parseFloat(b),parseFloat(c)]) @@ -447,10 +446,9 @@ export class Color s = s l = l a = a - lch = conv.hsl.lch([h,s,l]) def lcha - #lcha ||= conv.hsl.lch([h,s,l]).concat(a) + #lcha ||= toLchArray("hsla({h} {s}% {l}% / {a})") def alpha a = 1 new Color(name,h,s,l,a) @@ -476,7 +474,7 @@ export class Color # "{h.toFixed(2)},{s.toFixed(2)}%,{l.toFixed(2)}%" def toLchString - let [l,c,h] = lch + let [l,c,h,a] = lcha! `lcha({l.toFixed(2)} {c.toFixed(2)} {h.toFixed(2)}% / {a})` def c @@ -1125,15 +1123,10 @@ export class StyleTheme let val = expr[0][0] let [l,c,h,a] = [null,null,null,1] - let color - if val..lcha [l,c,h,a] = val.lcha! elif val.._resolvedValue isa Color - color = val.._resolvedValue - - if color - [l,c,h] = color.lch + [l,c,h,a] = val._resolvedValue.lcha! if typeof l == 'number' l = Math.round(l * 10) / 10 @@ -1142,10 +1135,12 @@ export class StyleTheme if typeof h == 'number' h = Math.round(h * 10) / 10 + if l != null o[`--u_{name}L`] = l o[`--u_{name}C`] = c o[`--u_{name}H`] = h.._resolvedValue ?? h # no? + # o[`--u_{name}N`] = l > 50 ? 0 : 100 o[`--u_{name}A`] = a ?? 1 # o[pre] = color.toLchString() @@ -1157,6 +1152,7 @@ export class StyleTheme # aliased colors if ns and typeof palette[ns] == 'string' + return $color(palette[ns] + name.slice(ns.length)) if ns == 'hue' diff --git a/packages/imba/test/apps/style/colormix.imba b/packages/imba/test/apps/style/colormix.imba index 566568888..a315f6c69 100644 --- a/packages/imba/test/apps/style/colormix.imba +++ b/packages/imba/test/apps/style/colormix.imba @@ -2,7 +2,7 @@ global css @root $alpha:0.3 #bg:#fa0006 # red is lch(54 106.85 40.86) - # lch(52.2 102 39.3) + # lch(53.3 104.3 40.2) #bg2:lch(50 50 100) #bg3:lch(50 50 100 / 0.5) @@ -10,7 +10,7 @@ global css @root tag App - css c:#bg bg:#bg(0 0.5c 1h) + css c:#bg <$a[c:#bg(l 0 h)]> <$b[c:#bg2]> <$c[c:#bg3]> @@ -22,8 +22,8 @@ let color = do(el) let app = imba.mount() -test do eq color(app),'lch(52.2 102 39.3)' -test do eq color(app.$a),'lch(52.2 0 39.3)' +test do eq color(app),'lch(53.3 104.3 40.2)' +test do eq color(app.$a),'lch(53.3 0 40.2)' test do eq color(app.$b),'lch(50 50 100)' test do eq color(app.$c),'lch(50 50 100 / 0.5)' test do eq color(app.$d),'lch(40 40 100 / 0.3)'