1 /**
2  * \file lengthcommon.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Matthias Ettrich
8  * \author John Levon
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12 
13 #include <config.h>
14 
15 #include "Length.h"
16 
17 #include "support/convert.h"
18 #include "support/gettext.h"
19 #include "support/lassert.h"
20 #include "support/lstrings.h"
21 
22 using namespace std;
23 using namespace lyx::support;
24 
25 
26 namespace lyx {
27 
28 // I am not sure if "mu" should be possible to select (Lgb)
29 
30 // the latex units
31 char const * const unit_name[] = {
32 	"bp", "cc", "cm", "dd", "em", "ex", "in", "mm", "mu",
33 	"pc", "pt", "sp",
34 	"text%",  "col%", "page%", "line%",
35 	"theight%", "pheight%", "baselineskip%", "" };
36 
37 int const num_units = int(sizeof(unit_name) / sizeof(unit_name[0]) - 1);
38 
39 // the LyX gui units
40 char const * const unit_name_gui[] = {
41 	N_("bp"), N_("cc[[unit of measure]]"), N_("cm"), N_("dd"), N_("em"),
42 	N_("ex"), N_("in[[unit of measure]]"), N_("mm"), N_("mu[[unit of measure]]"), N_("pc"),
43 	N_("pt"), N_("sp"), N_("Text Width %"),
44 	N_("Column Width %"), N_("Page Width %"), N_("Line Width %"),
45 	N_("Text Height %"), N_("Page Height %"), N_("Line Distance %"), "" };
46 
47 
unitFromString(string const & data)48 Length::UNIT unitFromString(string const & data)
49 {
50 	int i = 0;
51 	while (i < num_units && data != unit_name[i])
52 		++i;
53 	return static_cast<Length::UNIT>(i);
54 }
55 
56 
57 namespace {
58 
59 /// skip n characters of input
lyx_advance(string & data,size_t n)60 inline void lyx_advance(string & data, size_t n)
61 {
62 	data.erase(0, n);
63 }
64 
65 
66 /// return true when the input is at the end
isEndOfData(string const & data)67 inline bool isEndOfData(string const & data)
68 {
69 	return ltrim(data).empty();
70 }
71 
72 
73 /**
74  * nextToken -  return the next token in the input
75  * @param data input string
76  * @param number_index the current position in the number array
77  * @param unit_index the current position in the unit array
78  * @return a char representing the type of token returned
79  *
80  * The possible return values are :
81  *	+	stretch indicator for glue length
82  *	-	shrink indicator for glue length
83  *	n	a numeric value (stored in number array)
84  *	u	a unit type (stored in unit array)
85  *	E	parse error
86  */
nextToken(string & data,double * number,int & number_index,Length::UNIT * unit,int & unit_index)87 char nextToken(string & data, double * number, int & number_index,
88                Length::UNIT * unit, int & unit_index)
89 {
90 	data = ltrim(data);
91 
92 	if (data.empty())
93 		return '\0';
94 
95 	if (data[0] == '+') {
96 		lyx_advance(data, 1);
97 		return '+';
98 	}
99 
100 	if (prefixIs(data, "plus")) {
101 		lyx_advance(data, 4);
102 		return '+';
103 	}
104 
105 	if (data[0] == '-') {
106 		lyx_advance(data, 1);
107 		return '-';
108 	}
109 
110 	if (prefixIs(data, "minus")) {
111 		lyx_advance(data, 5);
112 		return '-';
113 	}
114 
115 	size_t i = data.find_first_not_of("0123456789.");
116 
117 	if (i != 0) {
118 		if (number_index > 3)
119 			return 'E';
120 
121 		string buffer;
122 
123 		// we have found some number
124 		if (i == string::npos) {
125 			buffer = data;
126 			i = data.size() + 1;
127 		} else {
128 			buffer = data.substr(0, i);
129 		}
130 
131 		lyx_advance(data, i);
132 
133 		if (isStrDbl(buffer)) {
134 			number[number_index] = convert<double>(buffer);
135 			++number_index;
136 			return 'n';
137 		}
138 		return 'E';
139 	}
140 
141 	i = data.find_first_not_of("abcdefghijklmnopqrstuvwxyz%");
142 	if (i != 0) {
143 		if (unit_index > 3)
144 			return 'E';
145 
146 		string buffer;
147 
148 		// we have found some alphabetical string
149 		if (i == string::npos) {
150 			buffer = data;
151 			i = data.size() + 1;
152 		} else {
153 			buffer = data.substr(0, i);
154 		}
155 
156 		// possibly we have "mmplus" string or similar
157 		if (buffer.size() > 5 &&
158 				(buffer.substr(2, 4) == string("plus") ||
159 				 buffer.substr(2, 5) == string("minus")))
160 		{
161 			lyx_advance(data, 2);
162 			unit[unit_index] = unitFromString(buffer.substr(0, 2));
163 		} else {
164 			lyx_advance(data, i);
165 			unit[unit_index] = unitFromString(buffer);
166 		}
167 
168 		if (unit[unit_index] != Length::UNIT_NONE) {
169 			++unit_index;
170 			return 'u';
171 		}
172 		return 'E';  // Error
173 	}
174 	return 'E';  // Error
175 }
176 
177 
178 /// latex representation of a vspace
179 struct LaTeXLength {
180 	char const * pattern;
181 	int  plus_val_index;
182 	int  minus_val_index;
183 	int  plus_uni_index;
184 	int  minus_uni_index;
185 };
186 
187 
188 /// the possible formats for a vspace string
189 LaTeXLength table[] = {
190 	{ "nu",       0, 0, 0, 0 },
191 	{ "nu+nu",    2, 0, 2, 0 },
192 	{ "nu+nu-nu", 2, 3, 2, 3 },
193 	{ "nu+-nu",   2, 2, 2, 2 },
194 	{ "nu-nu",    0, 2, 0, 2 },
195 	{ "nu-nu+nu", 3, 2, 3, 2 },
196 	{ "nu-+nu",   2, 2, 2, 2 },
197 	{ "n+nu",     2, 0, 1, 0 },
198 	{ "n+n-nu",   2, 3, 1, 1 },
199 	{ "n+-nu",    2, 2, 1, 1 },
200 	{ "n-nu",     0, 2, 0, 1 },
201 	{ "n-n+nu",   3, 2, 1, 1 },
202 	{ "n-+nu",    2, 2, 1, 1 },
203 	{ "",         0, 0, 0, 0 }   // sentinel, must be empty
204 };
205 
206 
207 } // namespace
208 
209 
stringFromUnit(int unit)210 const char * stringFromUnit(int unit)
211 {
212 	if (unit < 0 || unit > num_units)
213 		return 0;
214 	return unit_name[unit];
215 }
216 
217 
isValidGlueLength(string const & data,GlueLength * result)218 bool isValidGlueLength(string const & data, GlueLength * result)
219 {
220 	// This parser is table-driven.  First, it constructs a "pattern"
221 	// that describes the sequence of tokens in "data".  For example,
222 	// "n-nu" means: number, minus sign, number, unit.  As we go along,
223 	// numbers and units are stored into static arrays.  Then, "pattern"
224 	// is searched in the "table".  If it is found, the associated
225 	// table entries tell us which number and unit should go where
226 	// in the Length structure.  Example: if "data" has the "pattern"
227 	// "nu+nu-nu", the associated table entries are "2, 3, 2, 3".
228 	// That means, "plus_val" is the second number that was seen
229 	// in the input, "minus_val" is the third number, and "plus_uni"
230 	// and "minus_uni" are the second and third units, respectively.
231 	// ("val" and "uni" are always the first items seen in "data".)
232 	// This is the most elegant solution I could find -- a straight-
233 	// forward approach leads to very long, tedious code that would be
234 	// much harder to understand and maintain. (AS)
235 
236 	if (data.empty()) {
237 		if (result)
238 			*result = GlueLength();
239 		return true;
240 	}
241 	string buffer = ltrim(data);
242 
243 	// To make isValidGlueLength recognize negative values as
244 	// the first number this little hack is needed:
245 	int val_sign = 1; // positive as default
246 	switch (buffer[0]) {
247 	case '-':
248 		lyx_advance(buffer, 1);
249 		val_sign = -1;
250 		break;
251 	case '+':
252 		lyx_advance(buffer, 1);
253 		break;
254 	default:
255 		break;
256 	}
257 	// end of hack
258 
259 	// used to return numeric values in parsing vspace
260 	double number[4] = { 0, 0, 0, 0 };
261 	// used to return unit types in parsing vspace
262 	Length::UNIT unit[4] = {Length::UNIT_NONE, Length::UNIT_NONE,
263 	                        Length::UNIT_NONE, Length::UNIT_NONE};
264 	int number_index = 1; // entries at index 0 are sentinels
265 	int unit_index = 1;   // entries at index 0 are sentinels
266 
267 	// construct "pattern" from "data"
268 	size_t const pattern_max_size = 20;
269 	string pattern;
270 	while (!isEndOfData(buffer)) {
271 		if (pattern.size() > pattern_max_size)
272 			return false;
273 		char const c = nextToken(buffer, number, number_index, unit,
274 				unit_index);
275 		if (c == 'E')
276 			return false;
277 		pattern.push_back(c);
278 	}
279 
280 	// search "pattern" in "table"
281 	size_t table_index = 0;
282 	while (pattern != table[table_index].pattern) {
283 		++table_index;
284 		if (!*table[table_index].pattern)
285 			return false;
286 	}
287 
288 	// Get the values from the appropriate places.  If an index
289 	// is zero, the corresponding array value is zero or UNIT_NONE,
290 	// so we needn't check this.
291 	if (result) {
292 		result->len_.value  (number[1] * val_sign);
293 		result->len_.unit   (unit[1]);
294 		result->plus_.value (number[table[table_index].plus_val_index]);
295 		result->plus_.unit  (unit  [table[table_index].plus_uni_index]);
296 		result->minus_.value(number[table[table_index].minus_val_index]);
297 		result->minus_.unit (unit  [table[table_index].minus_uni_index]);
298 	}
299 	return true;
300 }
301 
302 
isValidLength(string const & data,Length * result)303 bool isValidLength(string const & data, Length * result)
304 {
305 	// This is a trimmed down version of isValidGlueLength.
306 	// The parser may seem overkill for lengths without
307 	// glue, but since we already have it, using it is
308 	// easier than writing something from scratch.
309 	if (data.empty()) {
310 		if (result)
311 			*result = Length();
312 		return true;
313 	}
314 
315 	string buffer = data;
316 
317 	// To make isValidLength recognize negative values
318 	// this little hack is needed:
319 	int val_sign = 1; // positive as default
320 	switch (buffer[0]) {
321 	case '-':
322 		lyx_advance(buffer, 1);
323 		val_sign = -1;
324 		break;
325 	case '+':
326 		lyx_advance(buffer, 1);
327 		// fall through
328 	default:
329 		// no action
330 		break;
331 	}
332 	// end of hack
333 
334 	// used to return numeric values in parsing vspace
335 	double number[4] = { 0, 0, 0, 0 };
336 	// used to return unit types in parsing vspace
337 	Length::UNIT unit[4] = {Length::UNIT_NONE, Length::UNIT_NONE,
338 	                        Length::UNIT_NONE, Length::UNIT_NONE};
339 	int number_index = 1; // entries at index 0 are sentinels
340 	int unit_index = 1;   // entries at index 0 are sentinels
341 
342 	// construct "pattern" from "data"
343 	string pattern;
344 	while (!isEndOfData(buffer)) {
345 		if (pattern.size() > 2)
346 			return false;
347 		char const token = nextToken(buffer, number,
348 				number_index, unit, unit_index);
349 		if (token == 'E')
350 			return false;
351 		pattern += token;
352 	}
353 
354 	// only the most basic pattern is accepted here
355 	if (pattern != "nu")
356 		return false;
357 
358 	// It _was_ a correct length string.
359 	// Store away the values we found.
360 	if (result) {
361 		result->val_  = number[1] * val_sign;
362 		result->unit_ = unit[1];
363 	}
364 	return true;
365 }
366 
367 } // namespace lyx
368