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