1: <?php
2: /**
3: ##DOC-SIGNATURE##
4:
5: This file is part of WideImage.
6:
7: WideImage is free software; you can redistribute it and/or modify
8: it under the terms of the GNU Lesser General Public License as published by
9: the Free Software Foundation; either version 2.1 of the License, or
10: (at your option) any later version.
11:
12: WideImage is distributed in the hope that it will be useful,
13: but WITHOUT ANY WARRANTY; without even the implied warranty of
14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15: GNU Lesser General Public License for more details.
16:
17: You should have received a copy of the GNU Lesser General Public License
18: along with WideImage; if not, write to the Free Software
19: Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20:
21: * @package Internals
22: **/
23:
24: namespace WideImage;
25:
26:
27: use WideImage\Exception\InvalidCoordinateException;
28:
29: /**
30: * A utility class for smart coordinates
31: *
32: * @package Internals
33: **/
34: class Coordinate
35: {
36: protected static $coord_align = array('left', 'center', 'right', 'top', 'middle', 'bottom');
37: protected static $coord_numeric = array('[0-9]+', '[0-9]+\.[0-9]+', '[0-9]+%', '[0-9]+\.[0-9]+%');
38:
39: /**
40: * Parses a numeric or string representation of a corrdinate into a structure
41: *
42: * @param string $coord Smart coordinate
43: * @return array Parsed smart coordinate
44: */
45: public static function parse($c)
46: {
47: $tokens = array();
48: $operators = array('+', '-');
49:
50: $flush_operand = false;
51: $flush_operator = false;
52: $current_operand = '';
53: $current_operator = '';
54: $coordinate = strval($c);
55: $expr_len = strlen($coordinate);
56:
57: for ($i = 0; $i < $expr_len; $i++) {
58: $char = $coordinate[$i];
59:
60: if (in_array($char, $operators)) {
61: $flush_operand = true;
62: $flush_operator = true;
63: $current_operator = $char;
64: } else {
65: $current_operand .= $char;
66: if ($i == $expr_len - 1) {
67: $flush_operand = true;
68: }
69: }
70:
71: if ($flush_operand) {
72: if (trim($current_operand) != '') {
73: $tokens[] = array('type' => 'operand', 'value' => trim($current_operand));
74: }
75:
76: $current_operand = '';
77: $flush_operand = false;
78: }
79:
80: if ($flush_operator) {
81: $tokens[] = array('type' => 'operator', 'value' => $char);
82: $flush_operator = false;
83: }
84: }
85:
86: return $tokens;
87: }
88:
89: /**
90: * Evaluates the $coord relatively to $dim
91: *
92: * @param string $coord A numeric value or percent string
93: * @param int $dim Dimension
94: * @param int $sec_dim Secondary dimension (for align)
95: * @return int Calculated value
96: */
97: public static function evaluate($coord, $dim, $sec_dim = null)
98: {
99: $comp_regex = implode('|', self::$coord_align) . '|' . implode('|', self::$coord_numeric);
100:
101: if (preg_match("/^([+-])?({$comp_regex})$/", $coord, $matches)) {
102: $sign = intval($matches[1] . "1");
103: $val = $matches[2];
104:
105: if (in_array($val, self::$coord_align)) {
106: if ($sec_dim === null) {
107: switch ($val) {
108: case 'left':
109: case 'top':
110: return 0;
111: break;
112: case 'center':
113: case 'middle':
114: return $sign * intval($dim / 2);
115: break;
116: case 'right':
117: case 'bottom':
118: return $sign * $dim;
119: break;
120: default:
121: return null;
122: }
123: } else {
124: switch ($val) {
125: case 'left':
126: case 'top':
127: return 0;
128: break;
129: case 'center':
130: case 'middle':
131: return $sign * intval($dim / 2 - $sec_dim / 2);
132: break;
133: case 'right':
134: case 'bottom':
135: return $sign * ($dim - $sec_dim);
136: break;
137: default:
138: return null;
139: }
140: }
141: } elseif (substr($val, -1) === '%') {
142: return intval(round($sign * $dim * floatval(str_replace('%', '', $val)) / 100));
143: } else {
144: return $sign * intval(round($val));
145: }
146: }
147: }
148:
149: /**
150: * Calculates and fixes a smart coordinate into a numeric value
151: *
152: * @param mixed $value Smart coordinate, relative to $dim
153: * @param int $dim Coordinate to which $value is relative
154: * @param int $sec_dim Secondary dimension (for align)
155: * @return int Calculated value
156: */
157: public static function fix($value, $dim, $sec_dim = null)
158: {
159: $coord_tokens = self::parse($value);
160:
161: if (count($coord_tokens) == 0 || $coord_tokens[count($coord_tokens) - 1]['type'] != 'operand') {
162: throw new InvalidCoordinateException("Couldn't parse coordinate '$value' properly.");
163: }
164:
165: $value = 0;
166: $operation = 1;
167:
168: foreach ($coord_tokens as $token) {
169: if ($token['type'] == 'operand') {
170: $operand_value = self::evaluate($token['value'], $dim, $sec_dim);
171:
172: if ($operation == 1) {
173: $value = $value + $operand_value;
174: } elseif ($operation == -1) {
175: $value = $value - $operand_value;
176: } else {
177: throw new InvalidCoordinateException("Invalid coordinate syntax.");
178: }
179:
180: $operation = 0;
181: } elseif ($token['type'] == 'operator') {
182: if ($token['value'] == '-') {
183: if ($operation == 0) {
184: $operation = -1;
185: } else {
186: $operation = $operation * -1;
187: }
188: } elseif ($token['value'] == '+') {
189: if ($operation == 0) {
190: $operation = '1';
191: }
192: }
193: }
194: }
195:
196: return $value;
197: }
198: }
199: