github.com/evanw/esbuild@v0.21.4/internal/css_parser/css_decls_font.go (about) 1 package css_parser 2 3 import ( 4 "strconv" 5 "strings" 6 7 "github.com/evanw/esbuild/internal/css_ast" 8 "github.com/evanw/esbuild/internal/css_lexer" 9 ) 10 11 // Specification: https://drafts.csswg.org/css-fonts/#font-prop 12 // [ <font-style> || <font-variant-css2> || <font-weight> || <font-stretch-css3> ]? <font-size> [ / <line-height> ]? <font-family> 13 func (p *parser) mangleFont(tokens []css_ast.Token) []css_ast.Token { 14 var result []css_ast.Token 15 16 // Scan up to the font size 17 pos := 0 18 for ; pos < len(tokens); pos++ { 19 token := tokens[pos] 20 if isFontSize(token) { 21 break 22 } 23 24 switch token.Kind { 25 case css_lexer.TIdent: 26 switch strings.ToLower(token.Text) { 27 case "normal": 28 // "All subproperties of the font property are first reset to their initial values" 29 // This implies that "normal" doesn't do anything. Also all of the optional values 30 // contain "normal" as an option and they are unordered so it's impossible to say 31 // what property "normal" corresponds to. Just drop these tokens to save space. 32 continue 33 34 // <font-style> 35 case "italic": 36 case "oblique": 37 if pos+1 < len(tokens) && tokens[pos+1].IsAngle() { 38 result = append(result, token, tokens[pos+1]) 39 pos++ 40 continue 41 } 42 43 // <font-variant-css2> 44 case "small-caps": 45 46 // <font-weight> 47 case "bold", "bolder", "lighter": 48 result = append(result, p.mangleFontWeight(token)) 49 continue 50 51 // <font-stretch-css3> 52 case "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", 53 "semi-expanded", "expanded", "extra-expanded", "ultra-expanded": 54 55 default: 56 // All other tokens are unrecognized, so we bail if we hit one 57 return tokens 58 } 59 result = append(result, token) 60 61 case css_lexer.TNumber: 62 // "Only values greater than or equal to 1, and less than or equal to 63 // 1000, are valid, and all other values are invalid." 64 if value, err := strconv.ParseFloat(token.Text, 64); err != nil || value < 1 || value > 1000 { 65 return tokens 66 } 67 result = append(result, token) 68 69 default: 70 // All other tokens are unrecognized, so we bail if we hit one 71 return tokens 72 } 73 } 74 75 // <font-size> 76 if pos == len(tokens) { 77 return tokens 78 } 79 result = append(result, tokens[pos]) 80 pos++ 81 82 // / <line-height> 83 if pos < len(tokens) && tokens[pos].Kind == css_lexer.TDelimSlash { 84 if pos+1 == len(tokens) { 85 return tokens 86 } 87 result = append(result, tokens[pos], tokens[pos+1]) 88 pos += 2 89 90 // Remove the whitespace around the "/" character 91 if p.options.minifyWhitespace { 92 result[len(result)-3].Whitespace &= ^css_ast.WhitespaceAfter 93 result[len(result)-2].Whitespace = 0 94 result[len(result)-1].Whitespace &= ^css_ast.WhitespaceBefore 95 } 96 } 97 98 // <font-family> 99 if family, ok := p.mangleFontFamily(tokens[pos:]); ok { 100 if len(result) > 0 && len(family) > 0 && family[0].Kind != css_lexer.TString { 101 family[0].Whitespace |= css_ast.WhitespaceBefore 102 } 103 return append(result, family...) 104 } 105 return tokens 106 } 107 108 var fontSizeKeywords = map[string]bool{ 109 // <absolute-size>: https://drafts.csswg.org/css-fonts/#valdef-font-size-absolute-size 110 "xx-small": true, 111 "x-small": true, 112 "small": true, 113 "medium": true, 114 "large": true, 115 "x-large": true, 116 "xx-large": true, 117 "xxx-large": true, 118 119 // <relative-size>: https://drafts.csswg.org/css-fonts/#valdef-font-size-relative-size 120 "larger": true, 121 "smaller": true, 122 } 123 124 // Specification: https://drafts.csswg.org/css-fonts/#font-size-prop 125 func isFontSize(token css_ast.Token) bool { 126 // <length-percentage> 127 if token.Kind == css_lexer.TDimension || token.Kind == css_lexer.TPercentage { 128 return true 129 } 130 131 // <absolute-size> or <relative-size> 132 if token.Kind == css_lexer.TIdent { 133 _, ok := fontSizeKeywords[strings.ToLower(token.Text)] 134 return ok 135 } 136 137 return false 138 }