Files

348 lines
11 KiB
Go

package css_parser
import (
"strings"
"github.com/evanw/esbuild/internal/css_ast"
"github.com/evanw/esbuild/internal/css_lexer"
)
func turnPercentIntoNumberIfShorter(t *css_ast.Token) {
if t.Kind == css_lexer.TPercentage {
if shifted, ok := shiftDot(t.PercentageValue(), -2); ok && len(shifted) < len(t.Text) {
t.Kind = css_lexer.TNumber
t.Text = shifted
}
}
}
// https://www.w3.org/TR/css-transforms-1/#two-d-transform-functions
// https://drafts.csswg.org/css-transforms-2/#transform-functions
func (p *parser) mangleTransforms(tokens []css_ast.Token) []css_ast.Token {
for i := range tokens {
if token := &tokens[i]; token.Kind == css_lexer.TFunction {
if args := *token.Children; css_ast.TokensAreCommaSeparated(args) {
n := len(args)
switch strings.ToLower(token.Text) {
////////////////////////////////////////////////////////////////////////////////
// 2D transforms
case "matrix":
// specifies a 2D transformation in the form of a transformation
// matrix of the six values a, b, c, d, e, f.
if n == 11 {
// | a c 0 e |
// | b d 0 f |
// | 0 0 1 0 |
// | 0 0 0 1 |
a, b, c, d, e, f := args[0], args[2], args[4], args[6], args[8], args[10]
if b.IsZero() && c.IsZero() && e.IsZero() && f.IsZero() {
// | a 0 0 0 |
// | 0 d 0 0 |
// | 0 0 1 0 |
// | 0 0 0 1 |
if a.EqualIgnoringWhitespace(d) {
// "matrix(a, 0, 0, a, 0, 0)" => "scale(a)"
token.Text = "scale"
*token.Children = args[:1]
} else if d.IsOne() {
// "matrix(a, 0, 0, 1, 0, 0)" => "scaleX(a)"
token.Text = "scaleX"
*token.Children = args[:1]
} else if a.IsOne() {
// "matrix(1, 0, 0, d, 0, 0)" => "scaleY(d)"
token.Text = "scaleY"
*token.Children = args[6:7]
} else {
// "matrix(a, 0, 0, d, 0, 0)" => "scale(a, d)"
token.Text = "scale"
*token.Children = append(args[:2], d)
}
// Note: A "matrix" cannot be directly converted into a "translate"
// because "translate" requires units while "matrix" requires no
// units. I'm not sure exactly what the semantics are so I'm not
// sure if you can just add "px" or not. Even if that did work,
// you still couldn't substitute values containing "var()" since
// units would still not be substituted in that case.
}
}
case "translate":
// specifies a 2D translation by the vector [tx, ty], where tx is the
// first translation-value parameter and ty is the optional second
// translation-value parameter. If <ty> is not provided, ty has zero
// as a value.
if n == 1 {
args[0].TurnLengthOrPercentageIntoNumberIfZero()
} else if n == 3 {
tx, ty := &args[0], &args[2]
tx.TurnLengthOrPercentageIntoNumberIfZero()
ty.TurnLengthOrPercentageIntoNumberIfZero()
if ty.IsZero() {
// "translate(tx, 0)" => "translate(tx)"
*token.Children = args[:1]
} else if tx.IsZero() {
// "translate(0, ty)" => "translateY(ty)"
token.Text = "translateY"
*token.Children = args[2:]
}
}
case "translatex":
// specifies a translation by the given amount in the X direction.
if n == 1 {
// "translateX(tx)" => "translate(tx)"
token.Text = "translate"
args[0].TurnLengthOrPercentageIntoNumberIfZero()
}
case "translatey":
// specifies a translation by the given amount in the Y direction.
if n == 1 {
args[0].TurnLengthOrPercentageIntoNumberIfZero()
}
case "scale":
// specifies a 2D scale operation by the [sx,sy] scaling vector
// described by the 2 parameters. If the second parameter is not
// provided, it takes a value equal to the first. For example,
// scale(1, 1) would leave an element unchanged, while scale(2, 2)
// would cause it to appear twice as long in both the X and Y axes,
// or four times its typical geometric size.
if n == 1 {
turnPercentIntoNumberIfShorter(&args[0])
} else if n == 3 {
sx, sy := &args[0], &args[2]
turnPercentIntoNumberIfShorter(sx)
turnPercentIntoNumberIfShorter(sy)
if sx.EqualIgnoringWhitespace(*sy) {
// "scale(s, s)" => "scale(s)"
*token.Children = args[:1]
} else if sy.IsOne() {
// "scale(s, 1)" => "scaleX(s)"
token.Text = "scaleX"
*token.Children = args[:1]
} else if sx.IsOne() {
// "scale(1, s)" => "scaleY(s)"
token.Text = "scaleY"
*token.Children = args[2:]
}
}
case "scalex":
// specifies a 2D scale operation using the [sx,1] scaling vector,
// where sx is given as the parameter.
if n == 1 {
turnPercentIntoNumberIfShorter(&args[0])
}
case "scaley":
// specifies a 2D scale operation using the [1,sy] scaling vector,
// where sy is given as the parameter.
if n == 1 {
turnPercentIntoNumberIfShorter(&args[0])
}
case "rotate":
// specifies a 2D rotation by the angle specified in the parameter
// about the origin of the element, as defined by the
// transform-origin property. For example, rotate(90deg) would
// cause elements to appear rotated one-quarter of a turn in the
// clockwise direction.
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
}
// Note: This is considered a 2D transform even though it's specified
// in terms of a 3D transform because it doesn't trigger Safari's 3D
// transform bugs.
case "rotatez":
// same as rotate3d(0, 0, 1, <angle>), which is a 3d transform
// equivalent to the 2d transform rotate(<angle>).
if n == 1 {
// "rotateZ(angle)" => "rotate(angle)"
token.Text = "rotate"
args[0].TurnLengthIntoNumberIfZero()
}
case "skew":
// specifies a 2D skew by [ax,ay] for X and Y. If the second
// parameter is not provided, it has a zero value.
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
} else if n == 3 {
ax, ay := &args[0], &args[2]
ax.TurnLengthIntoNumberIfZero()
ay.TurnLengthIntoNumberIfZero()
if ay.IsZero() {
// "skew(ax, 0)" => "skew(ax)"
*token.Children = args[:1]
}
}
case "skewx":
// specifies a 2D skew transformation along the X axis by the given
// angle.
if n == 1 {
// "skewX(ax)" => "skew(ax)"
token.Text = "skew"
args[0].TurnLengthIntoNumberIfZero()
}
case "skewy":
// specifies a 2D skew transformation along the Y axis by the given
// angle.
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
}
////////////////////////////////////////////////////////////////////////////////
// 3D transforms
// Note: Safari has a bug where 3D transforms render differently than
// other transforms. This means we should not minify a 3D transform
// into a 2D transform or it will cause a rendering difference in
// Safari.
case "matrix3d":
// specifies a 3D transformation as a 4x4 homogeneous matrix of 16
// values in column-major order.
if n == 31 {
// | m0 m4 m8 m12 |
// | m1 m5 m9 m13 |
// | m2 m6 m10 m14 |
// | m3 m7 m11 m15 |
mask := uint32(0)
for i := 0; i < 16; i++ {
if arg := args[i*2]; arg.IsZero() {
mask |= 1 << i
} else if arg.IsOne() {
mask |= (1 << 16) << i
}
}
const onlyScale = 0b1000_0000_0000_0000_0111_1011_1101_1110
if (mask & onlyScale) == onlyScale {
// | m0 0 0 0 |
// | 0 m5 0 0 |
// | 0 0 m10 0 |
// | 0 0 0 1 |
sx, sy := args[0], args[10]
if sx.IsOne() && sy.IsOne() {
token.Text = "scaleZ"
*token.Children = args[20:21]
} else {
token.Text = "scale3d"
*token.Children = append(append(args[0:2], args[10:12]...), args[20])
}
}
// Note: A "matrix3d" cannot be directly converted into a "translate3d"
// because "translate3d" requires units while "matrix3d" requires no
// units. I'm not sure exactly what the semantics are so I'm not
// sure if you can just add "px" or not. Even if that did work,
// you still couldn't substitute values containing "var()" since
// units would still not be substituted in that case.
}
case "translate3d":
// specifies a 3D translation by the vector [tx,ty,tz], with tx,
// ty and tz being the first, second and third translation-value
// parameters respectively.
if n == 5 {
tx, ty, tz := &args[0], &args[2], &args[4]
tx.TurnLengthOrPercentageIntoNumberIfZero()
ty.TurnLengthOrPercentageIntoNumberIfZero()
tz.TurnLengthIntoNumberIfZero()
if tx.IsZero() && ty.IsZero() {
// "translate3d(0, 0, tz)" => "translateZ(tz)"
token.Text = "translateZ"
*token.Children = args[4:]
}
}
case "translatez":
// specifies a 3D translation by the vector [0,0,tz] with the given
// amount in the Z direction.
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
}
case "scale3d":
// specifies a 3D scale operation by the [sx,sy,sz] scaling vector
// described by the 3 parameters.
if n == 5 {
sx, sy, sz := &args[0], &args[2], &args[4]
turnPercentIntoNumberIfShorter(sx)
turnPercentIntoNumberIfShorter(sy)
turnPercentIntoNumberIfShorter(sz)
if sx.IsOne() && sy.IsOne() {
// "scale3d(1, 1, sz)" => "scaleZ(sz)"
token.Text = "scaleZ"
*token.Children = args[4:]
}
}
case "scalez":
// specifies a 3D scale operation using the [1,1,sz] scaling vector,
// where sz is given as the parameter.
if n == 1 {
turnPercentIntoNumberIfShorter(&args[0])
}
case "rotate3d":
// specifies a 3D rotation by the angle specified in last parameter
// about the [x,y,z] direction vector described by the first three
// parameters. A direction vector that cannot be normalized, such as
// [0,0,0], will cause the rotation to not be applied.
if n == 7 {
x, y, z, angle := &args[0], &args[2], &args[4], &args[6]
angle.TurnLengthIntoNumberIfZero()
if x.IsOne() && y.IsZero() && z.IsZero() {
// "rotate3d(1, 0, 0, angle)" => "rotateX(angle)"
token.Text = "rotateX"
*token.Children = args[6:]
} else if x.IsZero() && y.IsOne() && z.IsZero() {
// "rotate3d(0, 1, 0, angle)" => "rotateY(angle)"
token.Text = "rotateY"
*token.Children = args[6:]
}
}
case "rotatex":
// same as rotate3d(1, 0, 0, <angle>).
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
}
case "rotatey":
// same as rotate3d(0, 1, 0, <angle>).
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
}
case "perspective":
// specifies a perspective projection matrix. This matrix scales
// points in X and Y based on their Z value, scaling points with
// positive Z values away from the origin, and those with negative Z
// values towards the origin. Points on the z=0 plane are unchanged.
// The parameter represents the distance of the z=0 plane from the
// viewer.
if n == 1 {
args[0].TurnLengthIntoNumberIfZero()
}
}
// Trim whitespace at the ends
if args := *token.Children; len(args) > 0 {
args[0].Whitespace &= ^css_ast.WhitespaceBefore
args[len(args)-1].Whitespace &= ^css_ast.WhitespaceAfter
}
}
}
}
return tokens
}