NumberInfo   F
last analyzed

Complexity

Total Complexity 110

Size/Duplication

Total Lines 903
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 110
eloc 220
c 3
b 1
f 0
dl 0
loc 903
rs 2

46 Methods

Rating   Name   Duplication   Size   Complexity  
A setNumber() 0 28 6
A isBiggerThan() 0 3 1
A isSmallerEqual() 0 3 1
A isSmallerThan() 0 3 1
A isEqual() 0 3 1
A hasDecimals() 0 5 1
A isPixels() 0 3 2
A isEmpty() 0 3 1
A createValueKey() 0 8 3
A getValue() 0 3 1
A addPercent() 0 3 1
A ceilEven() 0 7 2
A hasUnits() 0 3 1
A __construct() 0 6 1
A isUnitInteger() 0 10 2
A isUnitDecimal() 0 10 2
A getNumber() 0 10 2
A subtractPercent() 0 3 1
A isEM() 0 3 2
A add() 0 23 5
A isBiggerEqual() 0 3 1
A isPercent() 0 3 2
A subtract() 0 23 5
A filterInfo() 0 14 5
A isEven() 0 3 2
A __toString() 0 7 2
A hasValue() 0 3 2
B percentOperation() 0 35 7
A _setValue() 0 11 2
A getUnits() 0 11 3
A isZeroOrEmpty() 0 3 2
A floorEven() 0 7 2
A getInstanceID() 0 3 1
A preProcess() 0 3 1
A isNegative() 0 3 2
A isPositive() 0 8 2
A setValue() 0 3 1
A getRawInfo() 0 3 1
A isZero() 0 8 2
A enablePostProcess() 0 4 1
A postProcess() 0 3 1
B parseValue() 0 55 8
A findUnits() 0 25 4
B parseStringValue() 0 48 7
A toCSS() 0 11 3
A toAttribute() 0 15 4

How to fix   Complexity   

Complex Class

Complex classes like NumberInfo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NumberInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * File containing the {@link NumberInfo} class
4
 *
5
 * @access public
6
 * @package Application Utils
7
 * @subpackage NumberInfo
8
 * @see NumberInfo
9
 */
10
11
declare(strict_types=1);
12
13
namespace AppUtils;
14
15
/**
16
 * Abstraction class for numeric values for elements: offers
17
 * an easy-to-use API to work with pixel values, percentage
18
 * values and the like.
19
 *
20
 * Usage: use the global function {@link parseNumber()} to
21
 * create a new instance of the class, then use the API to
22
 * work with it.
23
 *
24
 * Examples:
25
 *
26
 * <pre>
27
 * parseNumber(42);
28
 * parseNumber('15%');
29
 * parseNumber('5em');
30
 * </pre>
31
 *
32
 * Hint: {@link parseNumber()} will also recognize number info
33
 * instances, so you can safely pass an existing number
34
 * info to it.
35
 *
36
 * @access public
37
 * @package Application Utils
38
 * @subpackage NumberInfo
39
 * @author Sebastian Mordziol <[email protected]>
40
 */
41
class NumberInfo
42
{
43
   /**
44
    * @var string|int|float|null
45
    */
46
    protected $rawValue;
47
    
48
   /**
49
    * @var array<string,mixed>
50
    */
51
    protected array $info;
52
    
53
   /**
54
    * @var bool
55
    */
56
    protected bool $empty = false;
57
58
    /**
59
     * @var bool
60
     */
61
    protected bool $postProcess = false;
62
63
    /**
64
     * Units and whether they allow decimal values.
65
    * @var array<string,bool>
66
    */
67
    protected array $knownUnits = array(
68
        '%' => true,
69
        'rem' => true,
70
        'px' => false,
71
        'em' => true,
72
        'pt' => true,
73
        'vw' => true,
74
        'vh' => true,
75
        'ex' => true,
76
        'cm' => true,
77
        'mm' => true,
78
        'in' => true,
79
        'pc' => true
80
    );
81
82
    /**
83
     * @var int
84
     */
85
    private static int $instanceCounter = 0;
86
87
    /**
88
     * @var int
89
     */
90
    protected int $instanceID;
91
92
    /**
93
     * @param string|int|float|NumberInfo|NULL $value
94
     */
95
    public function __construct($value)
96
    {
97
        self::$instanceCounter++;
98
        $this->instanceID = self::$instanceCounter;
99
100
        $this->_setValue($value);
101
    }
102
103
    /**
104
     * Gets the ID of this NumberInfo instance: every unique
105
     * instance gets assigned an ID to be able to distinguish
106
     * them (mainly used in the unit tests, but also has a few
107
     * practical applications).
108
     *
109
     * @return int
110
     */
111
    public function getInstanceID() : int
112
    {
113
        return $this->instanceID;
114
    }
115
116
    /**
117
     * Sets the value of the number, including the units.
118
     *
119
     * @param string|int|float|NumberInfo|NULL $value e.g. "10", "45px", "100%", ... or an existing NumberInfo instance.
120
     * @return $this
121
     */
122
    public function setValue($value)
123
    {
124
        return $this->_setValue($value);
125
    }
126
127
    /**
128
     * @param string|int|float|NumberInfo|NULL $value
129
     * @return $this
130
     */
131
    protected function _setValue($value)
132
    {
133
        if($value instanceof NumberInfo) {
134
            $value = $value->getValue();
135
        }
136
137
        $this->rawValue = $value;
138
        $this->info = $this->parseValue($value);
139
        $this->empty = $this->info['empty'];
140
141
        return $this;
142
    }
143
    
144
   /**
145
    * Retrieves the raw, internal information array resulting
146
    * from the parsing of the number.
147
    *  
148
    * @return array<string,mixed>
149
    */
150
    public function getRawInfo() : array
151
    {
152
        return $this->info;
153
    }
154
    
155
   /**
156
    * Whether the number was empty (null or empty string).
157
    * @return boolean
158
    */
159
    public function isEmpty() : bool
160
    {
161
        return $this->empty;
162
    }
163
164
    /**
165
     * Whether the number is bigger than 0.
166
     *
167
     * NOTE: Empty numbers (NULL) will always return false.
168
     *
169
     * @return bool
170
     */
171
    public function isPositive() : bool
172
    {
173
        if($this->isEmpty())
174
        {
175
            return false;
176
        }
177
178
        return $this->getNumber() > 0;
179
    }
180
181
    /**
182
     * Whether the number is exactly `0`.
183
     *
184
     * @return boolean
185
     */
186
    public function isZero() : bool
187
    {
188
        if($this->isEmpty())
189
        {
190
            return false;
191
        }
192
193
        return (float)$this->getNumber() === 0.0;
194
    }
195
    
196
    public function isZeroOrEmpty() : bool
197
    {
198
        return $this->isEmpty() || $this->isZero();
199
    }
200
    
201
    /**
202
     * Whether the number has a value: this is true if
203
     * it is not empty, and has a non-zero value.
204
     *
205
     * @return boolean
206
     */
207
    public function hasValue() : bool
208
    {
209
        return !$this->isEmpty() && !$this->isZero();
210
    }
211
    
212
    /**
213
     * Whether the value is negative.
214
     * @return boolean
215
     */
216
    public function isNegative() : bool
217
    {
218
        return !$this->isEmpty() && $this->getNumber() < 0;
219
    }
220
    
221
    /**
222
     * Changes the stored number.
223
     *
224
     * NOTE: Will be ignored if the specified number's
225
     * units do not match.
226
     *
227
     * @param string|int|float|NULL|NumberInfo $number
228
     * @return $this
229
     */
230
    public function setNumber($number)
231
    {
232
        // Append the units if the value is a number,
233
        // so they can be inherited.
234
        if($this->hasUnits() && is_numeric($number))
235
        {
236
            $number .= $this->getUnits();
237
        }
238
239
        $new = parseNumber($number);
240
241
        if($new->isEmpty())
242
        {
243
            return $this;
244
        }
245
246
        if($new->getUnits() === $this->getUnits())
247
        {
248
            $value = $new->getNumber();
249
250
            if($this->hasUnits()) {
251
                $value .= $this->getUnits();
252
            }
253
254
            $this->_setValue($value);
255
        }
256
257
        return $this;
258
    }
259
    
260
    /**
261
     * Whether the number is a pixel value. This is true
262
     * also if the px suffix is omitted.
263
     * @return boolean
264
     */
265
    public function isPixels() : bool
266
    {
267
        return !$this->isEmpty() && $this->getUnits() === 'px';
268
    }
269
    
270
    /**
271
     * Whether the number is a percent value.
272
     * @return boolean
273
     */
274
    public function isPercent() : bool
275
    {
276
        return !$this->isEmpty() && $this->getUnits() === '%';
277
    }
278
279
    public function isEM() : bool
280
    {
281
        return !$this->isEmpty() && $this->getUnits() === 'em';
282
    }
283
    
284
    /**
285
     * Retrieves the numeric value without units.
286
     * @return float|int
287
     */
288
    public function getNumber()
289
    {
290
        $number = (float)$this->info['number'];
291
292
        if($this->hasDecimals())
293
        {
294
            return $number;
295
        }
296
297
        return intval($number);
298
    }
299
300
    public function hasDecimals() : bool
301
    {
302
        $number = (float)$this->info['number'];
303
304
        return floor($number) !== $number;
305
    }
306
    
307
    /**
308
     * Checks whether the number is an even number.
309
     * @return boolean
310
     */
311
    public function isEven() : bool
312
    {
313
        return !$this->isEmpty() && !($this->getNumber() & 1);
314
    }
315
    
316
    /**
317
     * Retrieves the units of the number. If no units
318
     * have been initially specified, this will always
319
     * return 'px'.
320
     *
321
     * NOTE: If the number itself is empty (NULL), this
322
     * will return an empty string.
323
     *
324
     * @return string
325
     */
326
    public function getUnits() : string
327
    {
328
        if($this->isEmpty()) {
329
            return '';
330
        }
331
332
        if(!$this->hasUnits()) {
333
            return 'px';
334
        }
335
        
336
        return $this->info['units'];
337
    }
338
    
339
    /**
340
     * Whether specific units have been specified for the number.
341
     * @return boolean
342
     */
343
    public function hasUnits() : bool
344
    {
345
        return !empty($this->info['units']);
346
    }
347
    
348
    /**
349
     * Retrieves the raw value as is, with or without units depending on how it was given.
350
     * @return string|int|float|NULL
351
     */
352
    public function getValue()
353
    {
354
        return $this->rawValue;
355
    }
356
    
357
    /**
358
     * Formats the number for use in a HTML attribute. If units were
359
     * specified, only percent are kept. All other units like px and the
360
     * like are stripped.
361
     *
362
     * @return string
363
     */
364
    public function toAttribute() : string
365
    {
366
        if($this->isEmpty()) {
367
            return '';
368
        }
369
        
370
        if($this->isZero()) {
371
            return '0';
372
        }
373
        
374
        if($this->isPercent()) {
375
            return $this->getNumber().$this->getUnits();
376
        }
377
        
378
        return (string)$this->getNumber();
379
    }
380
    
381
    /**
382
     * Formats the number for use in a CSS statement.
383
     * @return string
384
     */
385
    public function toCSS() : string
386
    {
387
        if($this->isEmpty()) {
388
            return '';
389
        }
390
        
391
        if($this->isZero()) {
392
            return '0';
393
        }
394
        
395
        return $this->getNumber().$this->getUnits();
396
    }
397
    
398
    public function __toString()
399
    {
400
        if($this->isEmpty()) {
401
            return '';
402
        }
403
        
404
        return (string)$this->getValue();
405
    }
406
    
407
    /**
408
     * Checks if this number is bigger than the specified
409
     * number.
410
     *
411
     * NOTE: Always returns false if the units are not the same.
412
     *
413
     * NOTE: If this number or the one being compared is empty
414
     * (NULL), this will return false even if it translates
415
     * to a `0` value.
416
     *
417
     * @param string|int|float|NULL|NumberInfo $number
418
     * @return boolean
419
     */
420
    public function isBiggerThan($number) : bool
421
    {
422
        return (new NumberInfo_Comparer($this, parseNumber($number)))->isBiggerThan();
423
    }
424
    
425
    /**
426
     * Checks if this number is smaller than the specified
427
     * number.
428
     *
429
     * NOTE: Always returns false if the units are not the same.
430
     *
431
     * NOTE: If this number or the one being compared is empty
432
     * (NULL), this will return false even if it translates
433
     * to a `0` value.
434
     *
435
     * @param string|int|float|NULL|NumberInfo $number
436
     * @return boolean
437
     */
438
    public function isSmallerThan($number) : bool
439
    {
440
        return (new NumberInfo_Comparer($this, parseNumber($number)))->isSmallerThan();
441
    }
442
443
    /**
444
     * Checks if this number is smaller than the specified
445
     * number.
446
     *
447
     * NOTE: Always returns false if the units are not the same.
448
     *
449
     * NOTE: If this number or the one being compared is empty
450
     * (NULL), this will return false even if it translates
451
     * to a `0` value.
452
     *
453
     * @param string|int|float|NULL|NumberInfo $number
454
     * @return boolean
455
     */
456
    public function isSmallerEqual($number) : bool
457
    {
458
        return (new NumberInfo_Comparer($this, parseNumber($number)))->isSmallerEqual();
459
    }
460
461
    /**
462
     * Checks if this number is bigger or equals the
463
     * specified number.
464
     *
465
     * NOTE: Always returns false if the units are not the same.
466
     *
467
     * NOTE: If this number or the one being compared is empty
468
     * (NULL), this will return false even if it translates
469
     * to a `0` value.
470
     *
471
     * @param string|int|float|NULL|NumberInfo $number
472
     * @return bool
473
     */
474
    public function isBiggerEqual($number) : bool
475
    {
476
        return (new NumberInfo_Comparer($this, parseNumber($number)))->isBiggerEqual();
477
    }
478
479
    /**
480
     * Checks if this number equals the specified number.
481
     *
482
     * NOTE: Always returns false if the units are not the same.
483
     *
484
     * NOTE: If this number or the one being compared is empty
485
     * (NULL), this will return false even if it translates
486
     * to a `0` value.
487
     *
488
     * @param string|int|float|NULL|NumberInfo $number
489
     * @return bool
490
     */
491
    public function isEqual($number) : bool
492
    {
493
        return (new NumberInfo_Comparer($this, parseNumber($number)))->isEqual();
494
    }
495
    
496
    /**
497
     * Adds the specified value to the current value, if
498
     * they are compatible - i.e. they have the same units
499
     * or a percentage.
500
     *
501
     * @param string|int|float|null|NumberInfo $value
502
     * @return $this
503
     */
504
    public function add($value)
505
    {
506
        if($this->isEmpty())
507
        {
508
            $this->setValue($value);
509
            return $this;
510
        }
511
        
512
        $number = parseNumber($value);
513
        
514
        if($number->getUnits() === $this->getUnits() || !$number->hasUnits())
515
        {
516
            $new = $this->getNumber() + $number->getNumber();
517
518
            if($this->hasUnits())
519
            {
520
                $new .= $this->getUnits();
521
            }
522
523
            $this->setValue($new);
524
        }
525
        
526
        return $this;
527
    }
528
    
529
    /**
530
     * Subtracts the specified value from the current value, if
531
     * they are compatible - i.e. they have the same units, or
532
     * a percentage.
533
     *
534
     * @param string|int|float|NumberInfo|NULL $value
535
     * @return $this
536
     */
537
    public function subtract($value)
538
    {
539
        if($this->isEmpty())
540
        {
541
            $this->setValue($value);
542
            return $this;
543
        }
544
        
545
        $number = parseNumber($value);
546
        
547
        if($number->getUnits() == $this->getUnits() || !$number->hasUnits())
548
        {
549
            $new = $this->getNumber() - $number->getNumber();
550
551
            if($this->hasUnits())
552
            {
553
                $new .= $this->getUnits();
554
            }
555
556
            $this->setValue($new);
557
        }
558
        
559
        return $this;
560
    }
561
562
    /**
563
     * Subtracts the specified percentage from the number.
564
     *
565
     * @param float $percent
566
     * @return $this
567
     */
568
    public function subtractPercent(float $percent)
569
    {
570
        return $this->percentOperation('-', $percent);
571
    }
572
    
573
    /**
574
     * Increases the current value by the specified percent amount.
575
     *
576
     * @param float $percent
577
     * @return $this
578
     */
579
    public function addPercent(float $percent)
580
    {
581
        return $this->percentOperation('+', $percent);
582
    }
583
584
    /**
585
     * @param string $operation
586
     * @param int|float $percent
587
     * @return $this
588
     */
589
    protected function percentOperation(string $operation, $percent)
590
    {
591
        if($this->isZeroOrEmpty()) {
592
            return $this;
593
        }
594
        
595
        $percent = parseNumber($percent);
596
597
        if($percent->hasUnits() && !$percent->isPercent())
598
        {
599
            return $this;
600
        }
601
        
602
        $number = $this->getNumber();
603
        $value = $number * $percent->getNumber() / 100;
604
        
605
        if($operation == '-') {
606
            $number = $number - $value;
607
        } else {
608
            $number = $number + $value;
609
        }
610
        
611
        if($this->isUnitInteger())
612
        {
613
            $number = intval($number);
614
        }
615
616
        if($this->hasUnits())
617
        {
618
            $number .= $this->getUnits();
619
        }
620
621
        $this->setValue($number);
622
        
623
        return $this;
624
    }
625
    
626
    public function isUnitInteger() : bool
627
    {
628
        $units = $this->getUnits();
629
630
        if(isset($this->knownUnits[$units]))
631
        {
632
            return !$this->knownUnits[$units];
633
        }
634
635
        return false;
636
    }
637
    
638
    public function isUnitDecimal() : bool
639
    {
640
        $units = $this->getUnits();
641
642
        if(isset($this->knownUnits[$units]))
643
        {
644
            return $this->knownUnits[$units];
645
        }
646
647
        return false;
648
    }
649
    
650
    /**
651
     * Returns an array with information about the number
652
     * and the units used with the number for use in CSS
653
     * style attributes or HTML attributes.
654
     *
655
     * Examples:
656
     *
657
     * 58 => array(
658
     *     'number' => 58,
659
     *     'units' => null
660
     * )
661
     *
662
     * 58px => array(
663
     *     'number' => 58,
664
     *     'units' => 'px'
665
     * )
666
     *
667
     * 20% => array(
668
     *     'number' => 20,
669
     *     'units' => '%'
670
     * )
671
     *
672
     * @param string|int|float|NULL $value
673
     * @return array<string,mixed>
674
     */
675
    private function parseValue($value) : array
676
    {
677
        static $cache = array();
678
        
679
        $key = $this->createValueKey($value);
680
681
        if(array_key_exists($key, $cache)) {
682
            return $cache[$key];
683
        }
684
        
685
        $cache[$key] = array(
686
            'units' => null,
687
            'empty' => false,
688
            'number' => null
689
        );
690
        
691
        if($key === '_EMPTY_') 
692
        {
693
            $cache[$key]['empty'] = true;
694
            return $cache[$key];
695
        }
696
        
697
        if($value === 0 || $value === '0') 
698
        {
699
            $cache[$key]['number'] = 0;
700
            $cache[$key] = $this->filterInfo($cache[$key]);
701
            return $cache[$key];
702
        }
703
        
704
        $test = trim((string)$value);
705
        
706
        if($test === '') 
707
        {
708
            $cache[$key]['empty'] = true;
709
            return $cache[$key];
710
        }
711
        
712
        // replace comma notation (which is only possible if it's a string)
713
        if(is_string($value))
714
        {
715
            $test = $this->preProcess($test, $cache, $value);
716
        }
717
        
718
        // convert to a number if it's numeric
719
        if(is_numeric($test)) 
720
        {
721
            $cache[$key]['number'] = (float)$test * 1;
722
            $cache[$key] = $this->filterInfo($cache[$key]);
723
            return $cache[$key];
724
        }
725
        
726
        // not numeric: there are possibly units specified in the string
727
        $cache[$key] = $this->parseStringValue($test);
728
        
729
        return $cache[$key];
730
    }
731
    
732
   /**
733
    * Parses a string number notation with units included, e.g. 14px, 50%...
734
    * 
735
    * @param string $test
736
    * @return array<string,mixed>
737
    */
738
    private function parseStringValue(string $test) : array
739
    {
740
        $number = null;
741
        $units = null;
742
        $empty = false;
743
        
744
        $found = $this->findUnits($test);
745
        if($found !== null) 
746
        {
747
            $number = $found['number'];
748
            $units = $found['units'];
749
        }
750
        
751
        // the filters have to restore the value
752
        if($this->postProcess)
753
        {
754
            $number = $this->postProcess($number, $test);
755
        }
756
        // empty number
757
        else if($number === '' || $number === null || is_bool($number))
758
        {
759
            $number = null;
760
            $empty = true;
761
        }
762
        // found a number
763
        else
764
        {
765
            $number = trim($number);
766
            
767
            // may be an arbitrary string in some cases
768
            if(!is_numeric($number))
769
            {
770
                $number = null;
771
                $empty = true;
772
            }
773
            else
774
            {
775
                $number = (float)$number * 1;
776
            }
777
        }
778
        
779
        $result = array(
780
            'units' => $units,
781
            'number' => $number,
782
            'empty' => $empty
783
        );
784
785
        return $this->filterInfo($result);
786
    }
787
    
788
   /**
789
    * Attempts to determine what kind of units are specified
790
    * in the string. Returns NULL if none could be matched.
791
    * 
792
    * @param string $value
793
    * @return array<string,mixed>|NULL
794
    */
795
    private function findUnits(string $value) : ?array
796
    {
797
        $vlength = strlen($value);
798
        $names = array_keys($this->knownUnits);
799
        
800
        foreach($names as $unit)
801
        {
802
            $ulength = strlen($unit);
803
            $start = $vlength-$ulength;
804
            if($start < 0) {
805
                continue;
806
            }
807
            
808
            $search = substr($value, $start, $ulength);
809
            
810
            if($search==$unit) 
811
            {
812
                return array(
813
                    'units' => $unit,
814
                    'number' => substr($value, 0, $start)
815
                );
816
            }
817
        }
818
        
819
        return null;
820
    }
821
    
822
   /**
823
    * Creates the cache key for the specified value.
824
    * 
825
    * @param mixed $value
826
    * @return string
827
    */
828
    private function createValueKey($value) : string
829
    {
830
        if(!is_string($value) && !is_numeric($value))
831
        {
832
            return '_EMPTY_';
833
        }
834
835
        return (string)$value;
836
    }
837
838
   /**
839
    * Called if explicitly enabled: allows filtering the 
840
    * number after the detection process has completed.
841
    * 
842
    * @param string|NULL $number The adjusted number
843
    * @param string $originalString The original value before it was parsed
844
    * @return string|null
845
    */
846
    protected function postProcess(?string $number, /** @scrutinizer ignore-unused */ string $originalString)
847
    {
848
        return $number;
849
    }
850
    
851
   /**
852
    * Filters the value before it is parsed, but only if it is a string.
853
    * 
854
    * NOTE: This may be overwritten in a subclass, to allow custom filtering
855
    * the values. An example of a use case would be a preprocessor for
856
    * variables in a templating system.
857
    * 
858
    * @param string $trimmedString The trimmed value.
859
    * @param array<string,mixed> $cache The internal values cache array.
860
    * @param string $originalValue The original value that the NumberInfo was created for.
861
    * @return string
862
    * 
863
    * @see NumberInfo::enablePostProcess()
864
    */
865
    protected function preProcess(string $trimmedString, /** @scrutinizer ignore-unused */ array &$cache, /** @scrutinizer ignore-unused */ string $originalValue) : string
866
    {
867
        return str_replace(',', '.', $trimmedString);
868
    }
869
    
870
   /**
871
    * Enables the post-processing so the postProcess method gets called.
872
    * This should be called in the {@link NumberInfo::preProcess()}
873
    * method as needed.
874
    * 
875
    * @return $this
876
    * @see NumberInfo::postProcess()
877
    */
878
    protected function enablePostProcess() : NumberInfo
879
    {
880
        $this->postProcess = true;
881
        return $this;
882
    }
883
    
884
   /**
885
    * Filters the number info array to adjust the units
886
    * and number according to the required rules.
887
    * 
888
    * @param array<string,mixed> $info
889
    * @return array<string,mixed>
890
    */
891
    protected function filterInfo(array $info) : array
892
    {
893
        $useUnits = 'px';
894
        if($info['units'] !== null) {
895
            $useUnits = $info['units'];
896
        }
897
        
898
        // the units are non-decimal: convert decimal values
899
        if($this->knownUnits[$useUnits] === false && !$info['empty'] && is_numeric($info['number']))
900
        {
901
            $info['number'] = intval($info['number']);
902
        }
903
        
904
        return $info;
905
    }
906
907
    /**
908
     * Rounds fractions down in the number, and
909
     * decreases it to the nearest even number
910
     * if necessary.
911
     *
912
     * Examples:
913
     *
914
     * - 4 -> 4
915
     * - 5 -> 4
916
     * - 5.2 -> 4
917
     * - 5.8 -> 4
918
     *
919
     * @return $this
920
     */
921
    public function floorEven()
922
    {
923
        $number = floor($this->getNumber());
924
925
        if($number % 2 == 1) $number--;
926
927
        return $this->setNumber($number);
928
    }
929
930
    /**
931
     * Rounds fractions up in the number, and
932
     * increases it to the nearest even number
933
     * if necessary.
934
     *
935
     * @return $this
936
     */
937
    public function ceilEven()
938
    {
939
        $number = ceil($this->getNumber());
940
941
        if($number % 2 == 1) $number++;
942
943
        return $this->setNumber($number);
944
    }
945
}
946