Skip to content

Commit

Permalink
css gradients: remove more implied positions
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 10, 2023
1 parent 9edc9d4 commit ce4f100
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 33 deletions.
88 changes: 75 additions & 13 deletions internal/css_parser/css_decls_gradient.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ func (p *parser) generateGradient(token css_ast.Token, gradient parsedGradient)
if len(children) > 0 {
children = append(children, commaToken)
}
if len(stop.positions) == 0 && stop.midpoint.Kind == css_lexer.T(0) {
stop.color.Whitespace &= ^css_ast.WhitespaceAfter
}
children = append(children, stop.color)
children = append(children, stop.positions...)
if stop.midpoint.Kind != css_lexer.T(0) {
Expand Down Expand Up @@ -189,6 +192,7 @@ func (p *parser) lowerAndMinifyGradient(token css_ast.Token, wouldClipColor *boo
}

// Potentially expand the gradient to handle unsupported features
didExpand := false
if lowerMidpoints || lowerColorSpaces || lowerInterpolation {
if colorStops, ok := tryToParseColorStops(gradient); ok {
hasColorSpace := false
Expand All @@ -213,6 +217,7 @@ func (p *parser) lowerAndMinifyGradient(token css_ast.Token, wouldClipColor *boo
}
tryToExpandGradient(token.Loc, &gradient, colorStops, gradient.leadingTokens, colorSpace, shorterHue)
}
didExpand = true
}
}
}
Expand Down Expand Up @@ -243,9 +248,79 @@ func (p *parser) lowerAndMinifyGradient(token css_ast.Token, wouldClipColor *boo
}
}

if p.options.minifySyntax || didExpand {
gradient.colorStops = removeImpliedPositions(gradient.kind, gradient.colorStops)
}

return p.generateGradient(token, gradient)
}

func removeImpliedPositions(kind gradientKind, colorStops []colorStop) []colorStop {
if len(colorStops) == 0 {
return colorStops
}

positions := make([]valueWithUnit, len(colorStops))
for i, stop := range colorStops {
if len(stop.positions) == 1 {
if pos, ok := tryToParseValue(stop.positions[0], kind); ok {
positions[i] = pos
continue
}
}
positions[i].value = math.NaN()
}

start := 0
for start < len(colorStops) {
if startPos := positions[start]; !math.IsNaN(startPos.value) {
end := start + 1
run:
for colorStops[end-1].midpoint.Kind == css_lexer.T(0) && end < len(colorStops) {
endPos := positions[end]
if math.IsNaN(endPos.value) || endPos.unit != startPos.unit {
break
}

// Check that all values in this run are implied. Interpolation is done
// using the start and end positions instead of the first and second
// positions because it's more accurate.
for i := start + 1; i < end; i++ {
t := float64(i-start) / float64(end-start)
impliedValue := startPos.value + (endPos.value-startPos.value)*t
if math.Abs(positions[i].value-impliedValue) > 0.01 {
break run
}
}
end++
}

// Clear out all implied values
if end-start > 1 {
for i := start + 1; i+1 < end; i++ {
colorStops[i].positions = nil
}
start = end - 1
continue
}
}
start++
}

if first := colorStops[0].positions; len(first) == 1 &&
((first[0].Kind == css_lexer.TPercentage && first[0].PercentageValue() == "0") ||
(first[0].Kind == css_lexer.TDimension && first[0].DimensionValue() == "0")) {
colorStops[0].positions = nil
}

if last := colorStops[len(colorStops)-1].positions; len(last) == 1 &&
last[0].Kind == css_lexer.TPercentage && last[0].PercentageValue() == "100" {
colorStops[len(colorStops)-1].positions = nil
}

return colorStops
}

func switchToSinglePositions(double []colorStop) (single []colorStop) {
for _, stop := range double {
for i := range stop.positions {
Expand Down Expand Up @@ -740,19 +815,6 @@ func tryToExpandGradient(
}
}

// Remove implied positions for neatness
if len(newColorStops) > 0 {
first := newColorStops[0].positions[0]
if (first.Kind == css_lexer.TPercentage && first.PercentageValue() == "0") ||
(first.Kind == css_lexer.TDimension && first.DimensionValue() == "0") {
newColorStops[0].positions = nil
}
last := newColorStops[len(newColorStops)-1].positions[0]
if last.Kind == css_lexer.TPercentage && last.PercentageValue() == "100" {
newColorStops[len(newColorStops)-1].positions = nil
}
}

gradient.leadingTokens = remaining
gradient.colorStops = newColorStops
return true
Expand Down
40 changes: 20 additions & 20 deletions internal/css_parser/css_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,8 +780,8 @@ func TestGradient(t *testing.T) {
expectPrintedLowerUnsupported(t, compat.HexRGBA, code,
"a {\n background:\n "+gradient+"(\n yellow,\n 25%,\n rgba(17, 34, 51, .267));\n}\n", "")
expectPrintedLowerUnsupported(t, compat.GradientMidpoints, code,
"a {\n background:\n "+gradient+"(\n #ffff00,\n #f2f303de 3.12%,\n #eced04d0 6.25%,\n "+
"#e1e306bd 12.5%,\n #cdd00ba2 25%,\n #a2a8147b 50%,\n #6873205d 75%,\n #11223344);\n}\n", "")
"a {\n background:\n "+gradient+"(\n #ffff00,\n #f2f303de,\n #eced04d0 6.25%,\n "+
"#e1e306bd 12.5%,\n #cdd00ba2 25%,\n #a2a8147b,\n #6873205d,\n #11223344);\n}\n", "")

// Double positions
code = "a { background: " + gradient + "(green, red 10%, red 20%, yellow 70% 80%, black) }"
Expand Down Expand Up @@ -821,14 +821,14 @@ func TestGradient(t *testing.T) {
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))}", "")
expectPrintedLowerUnsupported(t, compat.ColorFunctions, code,
"a {\n background:\n "+gradient+"(\n #ffff00,\n #ffe971 12.5%,\n #ffd472 25%,\n "+
"#ffab5f 50%,\n #ff7b45 75%,\n #ff5e38 87.5%,\n #ff5534 90.62%,\n #ff4c30 93.75%,\n "+
"#ff412c 96.88%,\n #ff0e0e);\n "+
"background:\n "+gradient+"(\n #ffff00,\n color(xyz 0.734 0.805 0.111) 12.5%,\n "+
"color(xyz 0.699 0.693 0.087) 25%,\n color(xyz 0.627 0.501 0.048) 50%,\n "+
"a {\n background:\n "+gradient+"(\n #ffff00,\n #ffe971,\n #ffd472 25%,\n "+
"#ffab5f,\n #ff7b45 75%,\n #ff5e38 87.5%,\n #ff5534,\n #ff4c30,\n "+
"#ff412c,\n #ff0e0e);\n "+
"background:\n "+gradient+"(\n #ffff00,\n color(xyz 0.734 0.805 0.111),\n "+
"color(xyz 0.699 0.693 0.087) 25%,\n color(xyz 0.627 0.501 0.048),\n "+
"color(xyz 0.556 0.348 0.019) 75%,\n color(xyz 0.521 0.284 0.009) 87.5%,\n "+
"color(xyz 0.512 0.27 0.006) 90.62%,\n color(xyz 0.504 0.256 0.004) 93.75%,\n "+
"color(xyz 0.495 0.242 0.002) 96.88%,\n color(xyz 0.487 0.229 0));\n}\n", "")
"color(xyz 0.512 0.27 0.006),\n color(xyz 0.504 0.256 0.004),\n "+
"color(xyz 0.495 0.242 0.002),\n color(xyz 0.487 0.229 0));\n}\n", "")

// Whitespace
code = "a { background: " + gradient + "(color-mix(in lab,red,green)calc(1px)calc(2px),color-mix(in lab,blue,red)calc(98%)calc(99%)) }"
Expand All @@ -851,27 +851,27 @@ func TestGradient(t *testing.T) {
"a {\n background: "+gradient+"(#ff0000, #008000);\n}\n", "")
expectPrintedLowerUnsupported(t, compat.GradientInterpolation,
"a { background: "+gradient+"(in srgb-linear, red, green) }",
"a {\n background:\n "+gradient+"(\n #ff0000,\n #fb1300 3.12%,\n #f81f00 6.25%,\n "+
"#f02e00 12.5%,\n #e14200 25%,\n #bc5c00 50%,\n #897000 75%,\n #637800 87.5%,\n "+
"#477c00 93.75%,\n #317e00 96.88%,\n #008000);\n}\n", "")
"a {\n background:\n "+gradient+"(\n #ff0000,\n #fb1300,\n #f81f00 6.25%,\n "+
"#f02e00 12.5%,\n #e14200 25%,\n #bc5c00,\n #897000 75%,\n #637800 87.5%,\n "+
"#477c00 93.75%,\n #317e00,\n #008000);\n}\n", "")
expectPrintedLowerUnsupported(t, compat.GradientInterpolation,
"a { background: "+gradient+"(in lab, red, green) }",
"a {\n background:\n "+gradient+"(\n #ff0000,\n color(xyz 0.396 0.211 0.019) 3.12%,\n "+
"a {\n background:\n "+gradient+"(\n #ff0000,\n color(xyz 0.396 0.211 0.019),\n "+
"color(xyz 0.38 0.209 0.02) 6.25%,\n color(xyz 0.35 0.205 0.02) 12.5%,\n "+
"color(xyz 0.294 0.198 0.02) 25%,\n color(xyz 0.2 0.183 0.022) 50%,\n "+
"color(xyz 0.294 0.198 0.02) 25%,\n color(xyz 0.2 0.183 0.022),\n "+
"color(xyz 0.129 0.168 0.024) 75%,\n color(xyz 0.101 0.161 0.025) 87.5%,\n "+
"color(xyz 0.089 0.158 0.025) 93.75%,\n color(xyz 0.083 0.156 0.025) 96.88%,\n #008000);\n}\n", "")
"color(xyz 0.089 0.158 0.025) 93.75%,\n color(xyz 0.083 0.156 0.025),\n #008000);\n}\n", "")

// Hue interpolation
expectPrintedLowerUnsupported(t, compat.GradientInterpolation,
"a { background: "+gradient+"(in hsl shorter hue, red, green) }",
"a {\n background:\n "+gradient+"(\n #ff0000,\n #df7000 25%,\n "+
"#bfbf00 50%,\n #50a000 75%,\n #008000);\n}\n", "")
"a {\n background:\n "+gradient+"(\n #ff0000,\n #df7000,\n "+
"#bfbf00,\n #50a000,\n #008000);\n}\n", "")
expectPrintedLowerUnsupported(t, compat.GradientInterpolation,
"a { background: "+gradient+"(in hsl longer hue, red, green) }",
"a {\n background:\n "+gradient+"(\n #ff0000,\n #ef0078 12.5%,\n "+
"#df00df 25%,\n #6800cf 37.5%,\n #0000c0 50%,\n #0058b0 62.5%,\n "+
"#00a0a0 75%,\n #009048 87.5%,\n #008000);\n}\n", "")
"a {\n background:\n "+gradient+"(\n #ff0000,\n #ef0078,\n "+
"#df00df,\n #6800cf,\n #0000c0,\n #0058b0,\n "+
"#00a0a0,\n #009048,\n #008000);\n}\n", "")
}
}

Expand Down

0 comments on commit ce4f100

Please sign in to comment.