Base64VLQ.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. /**
  3. * Encode / Decode Base64 VLQ.
  4. *
  5. * @package Less
  6. * @subpackage SourceMap
  7. */
  8. class Less_SourceMap_Base64VLQ {
  9. /**
  10. * Shift
  11. *
  12. * @var integer
  13. */
  14. private $shift = 5;
  15. /**
  16. * Mask
  17. *
  18. * @var integer
  19. */
  20. private $mask = 0x1F; // == (1 << shift) == 0b00011111
  21. /**
  22. * Continuation bit
  23. *
  24. * @var integer
  25. */
  26. private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
  27. /**
  28. * Char to integer map
  29. *
  30. * @var array
  31. */
  32. private $charToIntMap = array(
  33. 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
  34. 'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
  35. 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
  36. 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
  37. 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
  38. 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
  39. 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
  40. 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
  41. 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
  42. );
  43. /**
  44. * Integer to char map
  45. *
  46. * @var array
  47. */
  48. private $intToCharMap = array(
  49. 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
  50. 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
  51. 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
  52. 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
  53. 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
  54. 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
  55. 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
  56. 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
  57. 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
  58. 63 => '/',
  59. );
  60. /**
  61. * Constructor
  62. */
  63. public function __construct() {
  64. // I leave it here for future reference
  65. // foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
  66. // {
  67. // $this->charToIntMap[$char] = $i;
  68. // $this->intToCharMap[$i] = $char;
  69. // }
  70. }
  71. /**
  72. * Convert from a two-complement value to a value where the sign bit is
  73. * is placed in the least significant bit. For example, as decimals:
  74. * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
  75. * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
  76. * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
  77. * even on a 64 bit machine.
  78. * @param string $aValue
  79. */
  80. public function toVLQSigned( $aValue ) {
  81. return 0xffffffff & ( $aValue < 0 ? ( ( -$aValue ) << 1 ) + 1 : ( $aValue << 1 ) + 0 );
  82. }
  83. /**
  84. * Convert to a two-complement value from a value where the sign bit is
  85. * is placed in the least significant bit. For example, as decimals:
  86. * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
  87. * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
  88. * We assume that the value was generated with a 32 bit machine in mind.
  89. * Hence
  90. * 1 becomes -2147483648
  91. * even on a 64 bit machine.
  92. * @param integer $aValue
  93. */
  94. public function fromVLQSigned( $aValue ) {
  95. return $aValue & 1 ? $this->zeroFill( ~$aValue + 2, 1 ) | ( -1 - 0x7fffffff ) : $this->zeroFill( $aValue, 1 );
  96. }
  97. /**
  98. * Return the base 64 VLQ encoded value.
  99. *
  100. * @param string $aValue The value to encode
  101. * @return string The encoded value
  102. */
  103. public function encode( $aValue ) {
  104. $encoded = '';
  105. $vlq = $this->toVLQSigned( $aValue );
  106. do
  107. {
  108. $digit = $vlq & $this->mask;
  109. $vlq = $this->zeroFill( $vlq, $this->shift );
  110. if ( $vlq > 0 ) {
  111. $digit |= $this->continuationBit;
  112. }
  113. $encoded .= $this->base64Encode( $digit );
  114. } while ( $vlq > 0 );
  115. return $encoded;
  116. }
  117. /**
  118. * Return the value decoded from base 64 VLQ.
  119. *
  120. * @param string $encoded The encoded value to decode
  121. * @return integer The decoded value
  122. */
  123. public function decode( $encoded ) {
  124. $vlq = 0;
  125. $i = 0;
  126. do
  127. {
  128. $digit = $this->base64Decode( $encoded[$i] );
  129. $vlq |= ( $digit & $this->mask ) << ( $i * $this->shift );
  130. $i++;
  131. } while ( $digit & $this->continuationBit );
  132. return $this->fromVLQSigned( $vlq );
  133. }
  134. /**
  135. * Right shift with zero fill.
  136. *
  137. * @param integer $a number to shift
  138. * @param integer $b number of bits to shift
  139. * @return integer
  140. */
  141. public function zeroFill( $a, $b ) {
  142. return ( $a >= 0 ) ? ( $a >> $b ) : ( $a >> $b ) & ( PHP_INT_MAX >> ( $b - 1 ) );
  143. }
  144. /**
  145. * Encode single 6-bit digit as base64.
  146. *
  147. * @param integer $number
  148. * @return string
  149. * @throws Exception If the number is invalid
  150. */
  151. public function base64Encode( $number ) {
  152. if ( $number < 0 || $number > 63 ) {
  153. throw new Exception( sprintf( 'Invalid number "%s" given. Must be between 0 and 63.', $number ) );
  154. }
  155. return $this->intToCharMap[$number];
  156. }
  157. /**
  158. * Decode single 6-bit digit from base64
  159. *
  160. * @param string $char
  161. * @return number
  162. * @throws Exception If the number is invalid
  163. */
  164. public function base64Decode( $char ) {
  165. if ( !array_key_exists( $char, $this->charToIntMap ) ) {
  166. throw new Exception( sprintf( 'Invalid base 64 digit "%s" given.', $char ) );
  167. }
  168. return $this->charToIntMap[$char];
  169. }
  170. }