]> git.lyx.org Git - features.git/blob - src/vspace.C
dont use pragma impementation and interface anymore
[features.git] / src / vspace.C
1 /**
2  * \file vspace.C
3  * Copyright 1995-2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author Matthias Ettrich
7  */
8
9 #include <config.h>
10
11 #include "vspace.h"
12 #include "lengthcommon.h"
13 #include "buffer.h"
14 #include "lyxrc.h"
15 #include "BufferView.h"
16 #include "support/LAssert.h"
17
18 #include "support/lstrings.h"
19
20 #include <cstdio>
21
22 #ifndef CXX_GLOBAL_CSTD
23 using std::sscanf;
24 #endif
25
26 namespace {
27
28 /// used to return numeric values in parsing vspace
29 double number[4] = { 0, 0, 0, 0 };
30 /// used to return unit types in parsing vspace
31 LyXLength::UNIT unit[4] = { LyXLength::UNIT_NONE,
32                             LyXLength::UNIT_NONE,
33                             LyXLength::UNIT_NONE,
34                             LyXLength::UNIT_NONE };
35
36 /// the current position in the number array
37 int number_index;
38 /// the current position in the unit array
39 int unit_index;
40
41 /// skip n characters of input
42 inline
43 void lyx_advance(string & data, string::size_type n)
44 {
45         data.erase(0, n);
46 }
47
48 /// return true when the input is at the end
49 inline
50 bool isEndOfData(string const & data)
51 {
52         return ltrim(data).empty();
53 }
54
55 /**
56  * nextToken -  return the next token in the input
57  * @param data input string
58  * @return a char representing the type of token returned
59  *
60  * The possible return values are :
61  *      +       stretch indicator for glue length
62  *      -       shrink indicator for glue length
63  *      n       a numeric value (stored in number array)
64  *      u       a unit type (stored in unit array)
65  *      E       parse error
66  */
67 char nextToken(string & data)
68 {
69         data = ltrim(data);
70         if (data.empty())
71                 return '\0';
72         else if (data[0] == '+') {
73                 lyx_advance(data, 1);
74                 return '+';
75         } else if (prefixIs(data, "plus")) {
76                 lyx_advance(data, 4);
77                 return '+';
78         } else if (data[0] == '-') {
79                 lyx_advance(data, 1);
80                 return '-';
81         } else if (prefixIs(data, "minus")) {
82                 lyx_advance(data, 5);
83                 return '-';
84         } else {
85                 string::size_type i = data.find_first_not_of("0123456789.");
86
87                 if (i != 0) {
88                         if (number_index > 3) return 'E';
89
90                         string buffer;
91
92                         // we have found some number
93                         if (i == string::npos) {
94                                 buffer = data;
95                                 i = data.size() + 1;
96                         } else
97                                 buffer = data.substr(0, i);
98
99                         lyx_advance(data, i);
100
101                         if (isStrDbl(buffer)) {
102                                 number[number_index] = strToDbl(buffer);
103                                 ++number_index;
104                                 return 'n';
105                         } else return 'E';
106                 }
107
108                 i = data.find_first_not_of("abcdefghijklmnopqrstuvwxyz%");
109                 if (i != 0) {
110                         if (unit_index > 3) return 'E';
111
112                         string buffer;
113
114                         // we have found some alphabetical string
115                         if (i == string::npos) {
116                                 buffer = data;
117                                 i = data.size() + 1;
118                         } else
119                                 buffer = data.substr(0, i);
120
121                         // possibly we have "mmplus" string or similar
122                         if (buffer.size() > 5 && (buffer.substr(2,4) == string("plus") || buffer.substr(2,5) == string("minus"))) {
123                                 lyx_advance(data, 2);
124                                 unit[unit_index] = unitFromString(buffer.substr(0, 2));
125                         } else {
126                                 lyx_advance(data, i);
127                                 unit[unit_index] = unitFromString(buffer);
128                         }
129
130                         if (unit[unit_index] != LyXLength::UNIT_NONE) {
131                                 ++unit_index;
132                                 return 'u';
133                         } else return 'E';  // Error
134                 }
135                 return 'E';  // Error
136         }
137 }
138
139
140 /// latex representation of a vspace
141 struct LaTeXLength {
142         char const * pattern;
143         int  plus_val_index;
144         int  minus_val_index;
145         int  plus_uni_index;
146         int  minus_uni_index;
147 };
148
149
150 /// the possible formats for a vspace string
151 LaTeXLength table[] = {
152         { "nu",       0, 0, 0, 0 },
153         { "nu+nu",    2, 0, 2, 0 },
154         { "nu+nu-nu", 2, 3, 2, 3 },
155         { "nu+-nu",   2, 2, 2, 2 },
156         { "nu-nu",    0, 2, 0, 2 },
157         { "nu-nu+nu", 3, 2, 3, 2 },
158         { "nu-+nu",   2, 2, 2, 2 },
159         { "n+nu",     2, 0, 1, 0 },
160         { "n+n-nu",   2, 3, 1, 1 },
161         { "n+-nu",    2, 2, 1, 1 },
162         { "n-nu",     0, 2, 0, 1 },
163         { "n-n+nu",   3, 2, 1, 1 },
164         { "n-+nu",    2, 2, 1, 1 },
165         { "",         0, 0, 0, 0 }   // sentinel, must be empty
166 };
167
168
169 } // namespace anon
170
171 const char * stringFromUnit(int unit)
172 {
173         if (unit < 0 || unit >= num_units)
174                 return 0;
175         return unit_name[unit];
176 }
177
178
179 bool isValidGlueLength(string const & data, LyXGlueLength * result)
180 {
181         // This parser is table-driven.  First, it constructs a "pattern"
182         // that describes the sequence of tokens in "data".  For example,
183         // "n-nu" means: number, minus sign, number, unit.  As we go along,
184         // numbers and units are stored into static arrays.  Then, "pattern"
185         // is searched in the "table".  If it is found, the associated
186         // table entries tell us which number and unit should go where
187         // in the LyXLength structure.  Example: if "data" has the "pattern"
188         // "nu+nu-nu", the associated table entries are "2, 3, 2, 3".
189         // That means, "plus_val" is the second number that was seen
190         // in the input, "minus_val" is the third number, and "plus_uni"
191         // and "minus_uni" are the second and third units, respectively.
192         // ("val" and "uni" are always the first items seen in "data".)
193         // This is the most elegant solution I could find -- a straight-
194         // forward approach leads to very long, tedious code that would be
195         // much harder to understand and maintain. (AS)
196
197         if (data.empty())
198                 return true;
199         string buffer = ltrim(data);
200
201         // To make isValidGlueLength recognize negative values as
202         // the first number this little hack is needed:
203         int val_sign = 1; // positive as default
204         switch (buffer[0]) {
205         case '-':
206                 lyx_advance(buffer, 1);
207                 val_sign = -1;
208                 break;
209         case '+':
210                 lyx_advance(buffer, 1);
211                 // fall through
212         default:
213                 // no action
214                 break;
215         }
216         // end of hack
217
218         int  pattern_index = 0;
219         int  table_index = 0;
220         char pattern[20];
221
222         number_index = 1;
223         unit_index = 1;  // entries at index 0 are sentinels
224
225         // construct "pattern" from "data"
226         while (!isEndOfData (buffer)) {
227                 if (pattern_index > 20) return false;
228                 pattern[pattern_index] = nextToken (buffer);
229                 if (pattern[pattern_index] == 'E') return false;
230                 ++pattern_index;
231         }
232         pattern[pattern_index] = '\0';
233
234         // search "pattern" in "table"
235         table_index = 0;
236         while (compare(pattern, table[table_index].pattern)) {
237                 ++table_index;
238                 if (!*table[table_index].pattern)
239                         return false;
240         }
241
242         // Get the values from the appropriate places.  If an index
243         // is zero, the corresponding array value is zero or UNIT_NONE,
244         // so we needn't check this.
245         if (result) {
246                 result->len_.value  (number[1] * val_sign);
247                 result->len_.unit   (unit[1]);
248                 result->plus_.value (number[table[table_index].plus_val_index]);
249                 result->plus_.unit  (unit  [table[table_index].plus_uni_index]);
250                 result->minus_.value(number[table[table_index].minus_val_index]);
251                 result->minus_.unit (unit  [table[table_index].minus_uni_index]);
252         }
253         return true;
254 }
255
256
257 bool isValidLength(string const & data, LyXLength * result)
258 {
259         // This is a trimmed down version of isValidGlueLength.
260         // The parser may seem overkill for lengths without
261         // glue, but since we already have it, using it is
262         // easier than writing something from scratch.
263         if (data.empty())
264                 return true;
265
266         string   buffer = data;
267         int      pattern_index = 0;
268         char     pattern[3];
269
270         // To make isValidLength recognize negative values
271         // this little hack is needed:
272         int val_sign = 1; // positive as default
273         switch (buffer[0]) {
274         case '-':
275                 lyx_advance(buffer, 1);
276                 val_sign = -1;
277                 break;
278         case '+':
279                 lyx_advance(buffer, 1);
280                 // fall through
281         default:
282                 // no action
283                 break;
284         }
285         // end of hack
286
287         number_index = unit_index = 1;  // entries at index 0 are sentinels
288
289         // construct "pattern" from "data"
290         while (!isEndOfData (buffer)) {
291                 if (pattern_index > 2)
292                         return false;
293                 pattern[pattern_index] = nextToken (buffer);
294                 if (pattern[pattern_index] == 'E')
295                         return false;
296                 ++pattern_index;
297         }
298         pattern[pattern_index] = '\0';
299
300         // only the most basic pattern is accepted here
301         if (compare(pattern, "nu") != 0) return false;
302
303         // It _was_ a correct length string.
304         // Store away the values we found.
305         if (result) {
306                 result->val_  = number[1] * val_sign;
307                 result->unit_ = unit[1];
308         }
309         return true;
310 }
311
312
313 //
314 //  VSpace class
315 //
316
317 VSpace::VSpace()
318         : kind_(NONE), len_(), keep_(false)
319 {}
320
321
322 VSpace::VSpace(vspace_kind k)
323         : kind_(k), len_(), keep_(false)
324 {}
325
326
327 VSpace::VSpace(LyXLength const & l)
328         : kind_(LENGTH), len_(l), keep_(false)
329 {}
330
331
332 VSpace::VSpace(LyXGlueLength const & l)
333         : kind_(LENGTH), len_(l), keep_(false)
334 {}
335
336
337 VSpace::VSpace(string const & data)
338         : kind_(NONE), len_(), keep_(false)
339 {
340         if (data.empty())
341                 return;
342         double value;
343         string input  = rtrim(data);
344
345         string::size_type const length = input.length();
346
347         if (length > 1 && input[length - 1] == '*') {
348                 keep_ = true;
349                 input.erase(length - 1);
350         }
351
352         if      (prefixIs (input, "defskip"))    kind_ = DEFSKIP;
353         else if (prefixIs (input, "smallskip"))  kind_ = SMALLSKIP;
354         else if (prefixIs (input, "medskip"))    kind_ = MEDSKIP;
355         else if (prefixIs (input, "bigskip"))    kind_ = BIGSKIP;
356         else if (prefixIs (input, "vfill"))      kind_ = VFILL;
357         else if (isValidGlueLength(input, &len_)) kind_ = LENGTH;
358         else if (sscanf(input.c_str(), "%lf", &value) == 1) {
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 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 = bv->defaultHeight(); // [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 }