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