1<?php 2 3namespace Mpdf\Conversion; 4 5/** 6 * @link https://github.com/JeroenDeDauw/RomanNumbers 7 * @license GNU GPL v2+ 8 */ 9class DecToRoman 10{ 11 12 private $symbolMap; 13 14 public function __construct(array $symbolMap = []) 15 { 16 if ($symbolMap !== []) { 17 $this->symbolMap = $symbolMap; 18 } else { 19 $this->symbolMap = [['I', 'V'], ['X', 'L'], ['C', 'D'], ['M']]; 20 } 21 } 22 23 public function convert($number, $toUpper = true) 24 { 25 $this->ensureNumberIsAnInteger($number); 26 $this->ensureNumberIsWithinBounds($number); 27 28 return $this->constructRomanString($number, $toUpper); 29 } 30 31 private function ensureNumberIsAnInteger($number) 32 { 33 if (!is_int($number)) { 34 throw new \InvalidArgumentException('Can only translate integers to roman'); 35 } 36 } 37 38 private function ensureNumberIsWithinBounds($number) 39 { 40 if ($number < 1) { 41 throw new \OutOfRangeException('Numbers under one cannot be translated to roman'); 42 } 43 44 if ($number > $this->getUpperBound()) { 45 throw new \OutOfBoundsException('The provided number is to big to be fully translated to roman'); 46 } 47 } 48 49 public function getUpperBound() 50 { 51 $symbolGroupCount = count($this->symbolMap); 52 $valueOfOne = 10 ** ($symbolGroupCount - 1); 53 54 $hasFiveSymbol = array_key_exists(1, $this->symbolMap[$symbolGroupCount - 1]); 55 56 return $valueOfOne * ($hasFiveSymbol ? 9 : 4) - 1; 57 } 58 59 private function constructRomanString($number, $toUpper) 60 { 61 $romanNumber = ''; 62 63 $symbolMapCount = count($this->symbolMap); 64 for ($i = 0; $i < $symbolMapCount; $i++) { 65 $divisor = 10 ** ($i + 1); 66 $remainder = $number % $divisor; 67 $digit = $remainder / (10 ** $i); 68 69 $number -= $remainder; 70 $romanNumber = $this->formatDigit($digit, $i) . $romanNumber; 71 72 if ($number === 0) { 73 break; 74 } 75 } 76 77 if (!$toUpper) { 78 $romanNumber = strtolower($romanNumber); 79 } 80 81 return $romanNumber; 82 } 83 84 private function formatDigit($digit, $orderOfMagnitude) 85 { 86 if ($digit === 0) { 87 return ''; 88 } 89 90 if ($digit === 4 || $digit === 9) { 91 return $this->formatFourOrNine($digit, $orderOfMagnitude); 92 } 93 94 $romanNumber = ''; 95 96 if ($digit >= 5) { 97 $digit -= 5; 98 $romanNumber .= $this->getFiveSymbol($orderOfMagnitude); 99 } 100 101 $romanNumber .= $this->formatOneToThree($orderOfMagnitude, $digit); 102 103 return $romanNumber; 104 } 105 106 private function formatFourOrNine($digit, $orderOfMagnitude) 107 { 108 $firstSymbol = $this->getOneSymbol($orderOfMagnitude); 109 $secondSymbol = $digit === 4 110 ? $this->getFiveSymbol($orderOfMagnitude) 111 : $this->getTenSymbol($orderOfMagnitude); 112 113 return $firstSymbol . $secondSymbol; 114 } 115 116 private function formatOneToThree($orderOfMagnitude, $digit) 117 { 118 return str_repeat($this->getOneSymbol($orderOfMagnitude), $digit); 119 } 120 121 private function getOneSymbol($orderOfMagnitude) 122 { 123 return $this->symbolMap[$orderOfMagnitude][0]; 124 } 125 126 private function getFiveSymbol($orderOfMagnitude) 127 { 128 return $this->symbolMap[$orderOfMagnitude][1]; 129 } 130 131 private function getTenSymbol($orderOfMagnitude) 132 { 133 return $this->symbolMap[$orderOfMagnitude + 1][0]; 134 } 135 136} 137