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