@@ -553,6 +553,16 @@ protected function shortenFontWeights($content)
553553 */
554554 protected function shortenZeroes ($ content )
555555 {
556+ // we don't want to strip units in `calc()` expressions:
557+ // `5px - 0px` is valid, but `5px - 0` is not
558+ // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
559+ // `10 * 0` is invalid
560+ // best to just leave `calc()`s alone, even if they could be optimized
561+ // (which is a whole other undertaking, where units & order of
562+ // operations all need to be considered...)
563+ $ calcs = $ this ->findCalcs ($ content );
564+ $ content = str_replace ($ calcs , array_keys ($ calcs ), $ content );
565+
556566 // reusable bits of code throughout these regexes:
557567 // before & after are used to make sure we don't match lose unintended
558568 // 0-like values (e.g. in #000, or in http://url/1.0)
@@ -581,30 +591,15 @@ protected function shortenZeroes($content)
581591 // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
582592 $ content = preg_replace ('/ ' .$ before .'-?0+ ' .$ units .'? ' .$ after .'/ ' , '0 \\1 ' , $ content );
583593
584- // remove zeroes where they make no sense in calc: e.g. calc(100px - 0)
585- // the 0 doesn't have any effect, and this isn't even valid without unit
586- // strip all `+ 0` or `- 0` occurrences: calc(10% + 0) -> calc(10%)
587- // looped because there may be multiple 0s inside 1 group of parentheses
588- do {
589- $ previous = $ content ;
590- $ content = preg_replace ('/\(([^\(\)]+) [\+\-] 0( [^\(\)]+)?\)/ ' , '( \\1 \\2) ' , $ content );
591- } while ($ content !== $ previous );
592- // strip all `0 +` occurrences: calc(0 + 10%) -> calc(10%)
593- $ content = preg_replace ('/\(0 \+ ([^\(\)]+)\)/ ' , '( \\1) ' , $ content );
594- // strip all `0 -` occurrences: calc(0 - 10%) -> calc(-10%)
595- $ content = preg_replace ('/\(0 \- ([^\(\)]+)\)/ ' , '(- \\1) ' , $ content );
596- // I'm not going to attempt to optimize away `x * 0` instances:
597- // it's dumb enough code already that it likely won't occur, and it's
598- // too complex to do right (order of operations would have to be
599- // respected etc)
600- // what I cared about most here was fixing incorrectly truncated units
601-
602594 // IE doesn't seem to understand a unitless flex-basis value, so let's
603595 // add it in again (make it `%`, which is only 1 char: 0%, 0px, 0
604596 // anything, it's all just the same)
605597 $ content = preg_replace ('/flex:([^ ]+ [^ ]+ )0([;\}])/ ' , 'flex:${1}0%${2} ' , $ content );
606598 $ content = preg_replace ('/flex-basis:0([;\}])/ ' , 'flex-basis:0%${1} ' , $ content );
607599
600+ // restore `calc()` expressions
601+ $ content = str_replace (array_keys ($ calcs ), $ calcs , $ content );
602+
608603 return $ content ;
609604 }
610605
@@ -667,6 +662,39 @@ protected function stripWhitespace($content)
667662 return trim ($ content );
668663 }
669664
665+ /**
666+ * Find all `calc()` occurrences.
667+ *
668+ * @param string $content The CSS content to find `calc()`s in.
669+ *
670+ * @return string[]
671+ */
672+ protected function findCalcs ($ content )
673+ {
674+ $ results = array ();
675+ preg_match_all ('/calc(\(.+?)(?=$|;|calc\()/ ' , $ content , $ matches , PREG_SET_ORDER );
676+
677+ foreach ($ matches as $ match ) {
678+ $ length = strlen ($ match [1 ]);
679+ $ expr = '' ;
680+ $ opened = 0 ;
681+
682+ for ($ i = 0 ; $ i < $ length ; $ i ++) {
683+ $ char = $ match [1 ][$ i ];
684+ $ expr .= $ char ;
685+ if ($ char === '( ' ) {
686+ $ opened ++;
687+ } elseif ($ char === ') ' && --$ opened === 0 ) {
688+ break ;
689+ }
690+ }
691+
692+ $ results ['calc( ' .count ($ results ).') ' ] = 'calc ' .$ expr ;
693+ }
694+
695+ return $ results ;
696+ }
697+
670698 /**
671699 * Check if file is small enough to be imported.
672700 *
0 commit comments