Skip to content

Commit

Permalink
css gradients: handle color transition hints
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 9, 2023
1 parent e4c55af commit f260285
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 14 deletions.
53 changes: 39 additions & 14 deletions internal/css_parser/css_decls_gradient.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type parsedGradient struct {
type colorStop struct {
positions []css_ast.Token
color css_ast.Token
hint css_ast.Token // Absent if "hint.Kind == css_lexer.T(0)"
}

func parseGradient(token css_ast.Token) (gradient parsedGradient, success bool) {
Expand Down Expand Up @@ -102,19 +103,36 @@ func parseGradient(token css_ast.Token) (gradient parsedGradient, success bool)
tokens = tokens[1:]
}

// Add the color stop
gradient.colorStops = append(gradient.colorStops, colorStop{
color: color,
positions: positions,
})

// Parse the comma
var hint css_ast.Token
if len(tokens) > 0 {
if tokens[0].Kind != css_lexer.TComma {
return
}
tokens = tokens[1:]
if len(tokens) == 0 {
return
}

// Parse the hint, if any
if len(tokens) > 0 && tokens[0].Kind.IsNumeric() {
hint = tokens[0]
tokens = tokens[1:]

// Followed by a mandatory comma
if len(tokens) == 0 || tokens[0].Kind != css_lexer.TComma {
return
}
tokens = tokens[1:]
}
}

// Add the color stop
gradient.colorStops = append(gradient.colorStops, colorStop{
color: color,
positions: positions,
hint: hint,
})
}

success = true
Expand All @@ -132,6 +150,9 @@ func (p *parser) generateGradient(token css_ast.Token, gradient parsedGradient)
}
children = append(children, stop.color)
children = append(children, stop.positions...)
if stop.hint.Kind != css_lexer.T(0) {
children = append(children, commaToken, stop.hint)
}
}

token.Children = &children
Expand Down Expand Up @@ -161,7 +182,7 @@ func (p *parser) lowerAndMinifyGradient(token css_ast.Token, wouldClipColor *boo
// Replace duplicated single positions with double positions
for i, stop := range gradient.colorStops {
if i > 0 && len(stop.positions) == 1 {
if prev := gradient.colorStops[i-1]; len(prev.positions) == 1 &&
if prev := gradient.colorStops[i-1]; len(prev.positions) == 1 && prev.hint.Kind == css_lexer.T(0) &&
css_ast.TokensEqual([]css_ast.Token{prev.color}, []css_ast.Token{stop.color}, nil) {
gradient.colorStops = switchToDoublePositions(gradient.colorStops)
break
Expand All @@ -175,27 +196,31 @@ func (p *parser) lowerAndMinifyGradient(token css_ast.Token, wouldClipColor *boo

func switchToSinglePositions(double []colorStop) (single []colorStop) {
for _, stop := range double {
for _, position := range stop.positions {
position.Whitespace = css_ast.WhitespaceBefore
stop.positions = []css_ast.Token{position}
single = append(single, stop)
for i := range stop.positions {
stop.positions[i].Whitespace = css_ast.WhitespaceBefore
}
if len(stop.positions) == 0 {
single = append(single, stop)
for len(stop.positions) > 1 {
clone := stop
clone.positions = stop.positions[:1]
clone.hint = css_ast.Token{}
single = append(single, clone)
stop.positions = stop.positions[1:]
}
single = append(single, stop)
}
return
}

func switchToDoublePositions(single []colorStop) (double []colorStop) {
for i := 0; i < len(single); i++ {
stop := single[i]
if i+1 < len(single) && len(stop.positions) == 1 {
if i+1 < len(single) && len(stop.positions) == 1 && stop.hint.Kind == css_lexer.T(0) {
if next := single[i+1]; len(next.positions) == 1 &&
css_ast.TokensEqual([]css_ast.Token{stop.color}, []css_ast.Token{next.color}, nil) {
double = append(double, colorStop{
color: stop.color,
positions: []css_ast.Token{stop.positions[0], next.positions[0]},
hint: next.hint,
})
i++
continue
Expand Down
31 changes: 31 additions & 0 deletions internal/css_parser/css_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,18 +760,49 @@ func TestGradient(t *testing.T) {
code = "a { background: " + gradient + "(yellow, #11223344) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(yellow, #11223344);\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(#ff0, #1234);\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(yellow,#11223344)}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(yellow, rgba(17, 34, 51, .267));\n}\n", "")

// Basic with positions
code = "a { background: " + gradient + "(yellow 10%, #11223344 90%) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(yellow 10%, #11223344 90%);\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(#ff0 10%, #1234 90%);\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(yellow 10%,#11223344 90%)}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(yellow 10%, rgba(17, 34, 51, .267) 90%);\n}\n", "")

// Basic with hints
code = "a { background: " + gradient + "(yellow, 25%, #11223344) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(yellow, 25%, #11223344);\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(#ff0, 25%, #1234);\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(yellow,25%,#11223344)}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(yellow, 25%, rgba(17, 34, 51, .267));\n}\n", "")

// Double positions
code = "a { background: " + gradient + "(green, red 10%, red 20%, yellow 70% 80%, black) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(green, red 10%, red 20%, yellow 70% 80%, black);\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(green, red 10% 20%, #ff0 70% 80%, #000);\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(green,red 10%,red 20%,yellow 70% 80%,black)}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(green, red 10%, red 20%, yellow 70%, yellow 80%, black);\n}\n", "")

// Double positions with hints
code = "a { background: " + gradient + "(green, red 10%, red 20%, 30%, yellow 70% 80%, 85%, black) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(green, red 10%, red 20%, 30%, yellow 70% 80%, 85%, black);\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(green, red 10% 20%, 30%, #ff0 70% 80%, 85%, #000);\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(green,red 10%,red 20%,30%,yellow 70% 80%,85%,black)}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(green, red 10%, red 20%, 30%, yellow 70%, yellow 80%, 85%, black);\n}\n", "")

// Non-double positions with hints
code = "a { background: " + gradient + "(green, red 10%, 1%, red 20%, black) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(green, red 10%, 1%, red 20%, black);\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(green, red 10%, 1%, red 20%, #000);\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(green,red 10%,1%,red 20%,black)}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(green, red 10%, 1%, red 20%, black);\n}\n", "")

// Out-of-gamut colors
code = "a { background: " + gradient + "(yellow, color(display-p3 1 0 0)) }"
expectPrinted(t, code, "a {\n background: "+gradient+"(yellow, color(display-p3 1 0 0));\n}\n", "")
expectPrintedMangle(t, code, "a {\n background: "+gradient+"(#ff0, color(display-p3 1 0 0));\n}\n", "")
expectPrintedMinify(t, code, "a{background:"+gradient+"(yellow,color(display-p3 1 0 0))}", "")
expectPrintedLower(t, code, "a {\n background: "+gradient+"(yellow, #ff0f0e);\n "+
"background: "+gradient+"(yellow, color(display-p3 1 0 0));\n}\n", "")

Expand Down

0 comments on commit f260285

Please sign in to comment.