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