From ce4f100f711c64dfe24946b2f2d8c6a02b6c700d Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 10 Dec 2023 11:00:13 -0500 Subject: [PATCH] css gradients: remove more implied positions --- internal/css_parser/css_decls_gradient.go | 88 +++++++++++++++++++---- internal/css_parser/css_parser_test.go | 40 +++++------ 2 files changed, 95 insertions(+), 33 deletions(-) diff --git a/internal/css_parser/css_decls_gradient.go b/internal/css_parser/css_decls_gradient.go index fe1b6095260..f1139eaa47c 100644 --- a/internal/css_parser/css_decls_gradient.go +++ b/internal/css_parser/css_decls_gradient.go @@ -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) { @@ -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 @@ -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 } } } @@ -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 { @@ -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 diff --git a/internal/css_parser/css_parser_test.go b/internal/css_parser/css_parser_test.go index 08f0578a081..abaeb7623c5 100644 --- a/internal/css_parser/css_parser_test.go +++ b/internal/css_parser/css_parser_test.go @@ -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) }" @@ -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%)) }" @@ -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", "") } }