]> git.lyx.org Git - lyx.git/blob - src/VSpace.cpp
LYX_CXX_GLOBAL_CSTD is not really useful anymore
[lyx.git] / src / VSpace.cpp
1 /**
2  * \file VSpace.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Matthias Ettrich
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "VSpace.h"
14
15 #include "Buffer.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
18 #include "support/gettext.h"
19 #include "Length.h"
20 #include "Text.h"
21 #include "TextMetrics.h" // for defaultRowHeight()
22
23 #include "support/convert.h"
24 #include "support/lstrings.h"
25
26 using namespace std;
27 using namespace lyx::support;
28
29 namespace lyx {
30
31 namespace {
32
33 /// used to return numeric values in parsing vspace
34 double number[4] = { 0, 0, 0, 0 };
35 /// used to return unit types in parsing vspace
36 Length::UNIT unit[4] = { Length::UNIT_NONE,
37                             Length::UNIT_NONE,
38                             Length::UNIT_NONE,
39                             Length::UNIT_NONE };
40
41 /// the current position in the number array
42 int number_index;
43 /// the current position in the unit array
44 int unit_index;
45
46 /// skip n characters of input
47 inline
48 void lyx_advance(string & data, string::size_type n)
49 {
50         data.erase(0, n);
51 }
52
53
54 /// return true when the input is at the end
55 inline
56 bool isEndOfData(string const & data)
57 {
58         return ltrim(data).empty();
59 }
60
61
62 /**
63  * nextToken -  return the next token in the input
64  * @param data input string
65  * @return a char representing the type of token returned
66  *
67  * The possible return values are :
68  *      +       stretch indicator for glue length
69  *      -       shrink indicator for glue length
70  *      n       a numeric value (stored in number array)
71  *      u       a unit type (stored in unit array)
72  *      E       parse error
73  */
74 char nextToken(string & data)
75 {
76         data = ltrim(data);
77
78         if (data.empty())
79                 return '\0';
80
81         if (data[0] == '+') {
82                 lyx_advance(data, 1);
83                 return '+';
84         }
85
86         if (prefixIs(data, "plus")) {
87                 lyx_advance(data, 4);
88                 return '+';
89         }
90
91         if (data[0] == '-') {
92                 lyx_advance(data, 1);
93                 return '-';
94         }
95
96         if (prefixIs(data, "minus")) {
97                 lyx_advance(data, 5);
98                 return '-';
99         }
100
101         string::size_type i = data.find_first_not_of("0123456789.");
102
103         if (i != 0) {
104                 if (number_index > 3)
105                         return 'E';
106
107                 string buffer;
108
109                 // we have found some number
110                 if (i == string::npos) {
111                         buffer = data;
112                         i = data.size() + 1;
113                 } else
114                         buffer = data.substr(0, i);
115
116                 lyx_advance(data, i);
117
118                 if (isStrDbl(buffer)) {
119                         number[number_index] = convert<double>(buffer);
120                         ++number_index;
121                         return 'n';
122                 }
123                 return 'E';
124         }
125
126         i = data.find_first_not_of("abcdefghijklmnopqrstuvwxyz%");
127         if (i != 0) {
128                 if (unit_index > 3)
129                         return 'E';
130
131                 string buffer;
132
133                 // we have found some alphabetical string
134                 if (i == string::npos) {
135                         buffer = data;
136                         i = data.size() + 1;
137                 } else
138                         buffer = data.substr(0, i);
139
140                 // possibly we have "mmplus" string or similar
141                 if (buffer.size() > 5 &&
142                                 (buffer.substr(2, 4) == string("plus") ||
143                                  buffer.substr(2, 5) == string("minus")))
144                 {
145                         lyx_advance(data, 2);
146                         unit[unit_index] = unitFromString(buffer.substr(0, 2));
147                 } else {
148                         lyx_advance(data, i);
149                         unit[unit_index] = unitFromString(buffer);
150                 }
151
152                 if (unit[unit_index] != Length::UNIT_NONE) {
153                         ++unit_index;
154                         return 'u';
155                 }
156                 return 'E';  // Error
157         }
158         return 'E';  // Error
159 }
160
161
162 /// latex representation of a vspace
163 struct LaTeXLength {
164         char const * pattern;
165         int  plus_val_index;
166         int  minus_val_index;
167         int  plus_uni_index;
168         int  minus_uni_index;
169 };
170
171
172 /// the possible formats for a vspace string
173 LaTeXLength table[] = {
174         { "nu",       0, 0, 0, 0 },
175         { "nu+nu",    2, 0, 2, 0 },
176         { "nu+nu-nu", 2, 3, 2, 3 },
177         { "nu+-nu",   2, 2, 2, 2 },
178         { "nu-nu",    0, 2, 0, 2 },
179         { "nu-nu+nu", 3, 2, 3, 2 },
180         { "nu-+nu",   2, 2, 2, 2 },
181         { "n+nu",     2, 0, 1, 0 },
182         { "n+n-nu",   2, 3, 1, 1 },
183         { "n+-nu",    2, 2, 1, 1 },
184         { "n-nu",     0, 2, 0, 1 },
185         { "n-n+nu",   3, 2, 1, 1 },
186         { "n-+nu",    2, 2, 1, 1 },
187         { "",         0, 0, 0, 0 }   // sentinel, must be empty
188 };
189
190
191 } // namespace anon
192
193 const char * stringFromUnit(int unit)
194 {
195         if (unit < 0 || unit > num_units)
196                 return 0;
197         return unit_name[unit];
198 }
199
200
201 bool isValidGlueLength(string const & data, GlueLength * result)
202 {
203         // This parser is table-driven.  First, it constructs a "pattern"
204         // that describes the sequence of tokens in "data".  For example,
205         // "n-nu" means: number, minus sign, number, unit.  As we go along,
206         // numbers and units are stored into static arrays.  Then, "pattern"
207         // is searched in the "table".  If it is found, the associated
208         // table entries tell us which number and unit should go where
209         // in the Length structure.  Example: if "data" has the "pattern"
210         // "nu+nu-nu", the associated table entries are "2, 3, 2, 3".
211         // That means, "plus_val" is the second number that was seen
212         // in the input, "minus_val" is the third number, and "plus_uni"
213         // and "minus_uni" are the second and third units, respectively.
214         // ("val" and "uni" are always the first items seen in "data".)
215         // This is the most elegant solution I could find -- a straight-
216         // forward approach leads to very long, tedious code that would be
217         // much harder to understand and maintain. (AS)
218
219         if (data.empty())
220                 return true;
221         string buffer = ltrim(data);
222
223         // To make isValidGlueLength recognize negative values as
224         // the first number this little hack is needed:
225         int val_sign = 1; // positive as default
226         switch (buffer[0]) {
227         case '-':
228                 lyx_advance(buffer, 1);
229                 val_sign = -1;
230                 break;
231         case '+':
232                 lyx_advance(buffer, 1);
233                 break;
234         default:
235                 break;
236         }
237         // end of hack
238
239         int  pattern_index = 0;
240         int  table_index = 0;
241         char pattern[20];
242
243         number_index = 1;
244         unit_index = 1;  // entries at index 0 are sentinels
245
246         // construct "pattern" from "data"
247         while (!isEndOfData(buffer)) {
248                 if (pattern_index > 20)
249                         return false;
250                 pattern[pattern_index] = nextToken(buffer);
251                 if (pattern[pattern_index] == 'E')
252                         return false;
253                 ++pattern_index;
254         }
255         pattern[pattern_index] = '\0';
256
257         // search "pattern" in "table"
258         table_index = 0;
259         while (compare(pattern, table[table_index].pattern)) {
260                 ++table_index;
261                 if (!*table[table_index].pattern)
262                         return false;
263         }
264
265         // Get the values from the appropriate places.  If an index
266         // is zero, the corresponding array value is zero or UNIT_NONE,
267         // so we needn't check this.
268         if (result) {
269                 result->len_.value  (number[1] * val_sign);
270                 result->len_.unit   (unit[1]);
271                 result->plus_.value (number[table[table_index].plus_val_index]);
272                 result->plus_.unit  (unit  [table[table_index].plus_uni_index]);
273                 result->minus_.value(number[table[table_index].minus_val_index]);
274                 result->minus_.unit (unit  [table[table_index].minus_uni_index]);
275         }
276         return true;
277 }
278
279
280 bool isValidLength(string const & data, Length * result)
281 {
282         // This is a trimmed down version of isValidGlueLength.
283         // The parser may seem overkill for lengths without
284         // glue, but since we already have it, using it is
285         // easier than writing something from scratch.
286         if (data.empty())
287                 return true;
288
289         string   buffer = data;
290         int      pattern_index = 0;
291         char     pattern[3];
292
293         // To make isValidLength recognize negative values
294         // this little hack is needed:
295         int val_sign = 1; // positive as default
296         switch (buffer[0]) {
297         case '-':
298                 lyx_advance(buffer, 1);
299                 val_sign = -1;
300                 break;
301         case '+':
302                 lyx_advance(buffer, 1);
303                 // fall through
304         default:
305                 // no action
306                 break;
307         }
308         // end of hack
309
310         number_index = unit_index = 1;  // entries at index 0 are sentinels
311
312         // construct "pattern" from "data"
313         while (!isEndOfData(buffer)) {
314                 if (pattern_index > 2)
315                         return false;
316                 pattern[pattern_index] = nextToken(buffer);
317                 if (pattern[pattern_index] == 'E')
318                         return false;
319                 ++pattern_index;
320         }
321         pattern[pattern_index] = '\0';
322
323         // only the most basic pattern is accepted here
324         if (compare(pattern, "nu") != 0)
325                 return false;
326
327         // It _was_ a correct length string.
328         // Store away the values we found.
329         if (result) {
330                 result->val_  = number[1] * val_sign;
331                 result->unit_ = unit[1];
332         }
333         return true;
334 }
335
336
337 //
338 //  VSpace class
339 //
340
341 VSpace::VSpace()
342         : kind_(DEFSKIP), len_(), keep_(false)
343 {}
344
345
346 VSpace::VSpace(VSpaceKind k)
347         : kind_(k), len_(), keep_(false)
348 {}
349
350
351 VSpace::VSpace(Length const & l)
352         : kind_(LENGTH), len_(l), keep_(false)
353 {}
354
355
356 VSpace::VSpace(GlueLength const & l)
357         : kind_(LENGTH), len_(l), keep_(false)
358 {}
359
360
361 VSpace::VSpace(string const & data)
362         : kind_(DEFSKIP), len_(), keep_(false)
363 {
364         if (data.empty())
365                 return;
366
367         string input = rtrim(data);
368
369         string::size_type const length = input.length();
370
371         if (length > 1 && input[length - 1] == '*') {
372                 keep_ = true;
373                 input.erase(length - 1);
374         }
375
376         if (prefixIs(input, "defskip"))
377                 kind_ = DEFSKIP;
378         else if (prefixIs(input, "smallskip"))
379                 kind_ = SMALLSKIP;
380         else if (prefixIs(input, "medskip"))
381                 kind_ = MEDSKIP;
382         else if (prefixIs(input, "bigskip"))
383                 kind_ = BIGSKIP;
384         else if (prefixIs(input, "vfill"))
385                 kind_ = VFILL;
386         else if (isValidGlueLength(input, &len_))
387                 kind_ = LENGTH;
388         else if (isStrDbl(input)) {
389                 // This last one is for reading old .lyx files
390                 // without units in added_space_top/bottom.
391                 // Let unit default to centimeters here.
392                 kind_ = LENGTH;
393                 len_  = GlueLength(Length(convert<double>(input), Length::CM));
394         }
395 }
396
397
398 bool VSpace::operator==(VSpace const & other) const
399 {
400         if (kind_ != other.kind_)
401                 return false;
402
403         if (kind_ != LENGTH)
404                 return this->keep_ == other.keep_;
405
406         if (len_ != other.len_)
407                 return false;
408
409         return keep_ == other.keep_;
410 }
411
412
413 string const VSpace::asLyXCommand() const
414 {
415         string result;
416         switch (kind_) {
417         case DEFSKIP:   result = "defskip";      break;
418         case SMALLSKIP: result = "smallskip";    break;
419         case MEDSKIP:   result = "medskip";      break;
420         case BIGSKIP:   result = "bigskip";      break;
421         case VFILL:     result = "vfill";        break;
422         case LENGTH:    result = len_.asString(); break;
423         }
424         if (keep_)
425                 result += '*';
426         return result;
427 }
428
429
430 string const VSpace::asLatexCommand(BufferParams const & params) const
431 {
432         switch (kind_) {
433         case DEFSKIP:
434                 return params.getDefSkip().asLatexCommand(params);
435
436         case SMALLSKIP:
437                 return keep_ ? "\\vspace*{\\smallskipamount}" : "\\smallskip{}";
438
439         case MEDSKIP:
440                 return keep_ ? "\\vspace*{\\medskipamount}" : "\\medskip{}";
441
442         case BIGSKIP:
443                 return keep_ ? "\\vspace*{\\bigskipamount}" : "\\bigskip{}";
444
445         case VFILL:
446                 return keep_ ? "\\vspace*{\\fill}" : "\\vfill{}";
447
448         case LENGTH:
449                 return keep_ ? "\\vspace*{" + len_.asLatexString() + '}'
450                         : "\\vspace{" + len_.asLatexString() + '}';
451
452         default:
453                 BOOST_ASSERT(false);
454                 return string();
455         }
456 }
457
458
459 docstring const VSpace::asGUIName() const
460 {
461         docstring result;
462         switch (kind_) {
463         case DEFSKIP:
464                 result = _("Default skip");
465                 break;
466         case SMALLSKIP:
467                 result = _("Small skip");
468                 break;
469         case MEDSKIP:
470                 result = _("Medium skip");
471                 break;
472         case BIGSKIP:
473                 result = _("Big skip");
474                 break;
475         case VFILL:
476                 result = _("Vertical fill");
477                 break;
478         case LENGTH:
479                 result = from_ascii(len_.asString());
480                 break;
481         }
482         if (keep_)
483                 result += ", " + _("protected");
484         return result;
485 }
486
487
488 int VSpace::inPixels(BufferView const & bv) const
489 {
490         // Height of a normal line in pixels (zoom factor considered)
491         int const default_height = defaultRowHeight();
492
493         switch (kind_) {
494
495         case DEFSKIP:
496                 return bv.buffer().params().getDefSkip().inPixels(bv);
497
498         // This is how the skips are normally defined by LateX.
499         // But there should be some way to change this per document.
500         case SMALLSKIP:
501                 return default_height / 4;
502
503         case MEDSKIP:
504                 return default_height / 2;
505
506         case BIGSKIP:
507                 return default_height;
508
509         case VFILL:
510                 // leave space for the vfill symbol
511                 return 3 * default_height;
512
513         case LENGTH:
514                 return len_.len().inPixels(bv.workWidth());
515
516         default:
517                 BOOST_ASSERT(false);
518                 return 0;
519         }
520 }
521
522
523 } // namespace lyx