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