]> git.lyx.org Git - lyx.git/blob - src/vspace.C
In for a penny, in for a pound. Consistent use of // -*- C++ -*-
[lyx.git] / src / vspace.C
1 /* This file is part of
2  * ====================================================== 
3  * 
4  *           LyX, The Document Processor
5  *       
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2001 The LyX Team.
8  *
9  * ====================================================== */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation "vspace.h"
15 #endif
16
17 #include <stdio.h>
18
19 #include "lyx_main.h"
20 #include "buffer.h"
21 #include "vspace.h"
22 #include "lyxrc.h"
23 #include "lyxtext.h"
24 #include "BufferView.h"
25 #include "support/lstrings.h"
26
27
28 namespace {
29
30 /*  length units
31  */
32
33 int const num_units = LyXLength::UNIT_NONE;
34
35 // I am not sure if "mu" should be possible to select (Lgb)
36 char const * unit_name[num_units] = { "sp", "pt", "bp", "dd",
37                                       "mm", "pc", "cc", "cm",
38                                       "in", "ex", "em", "mu",
39                                       "%",  "c%", "p%", "l%" }; 
40
41
42 /*  The following static items form a simple scanner for
43  *  length strings, used by isValid[Glue]Length.  See comments there.
44  */
45 float           number[4] = { 0, 0, 0, 0 };
46 LyXLength::UNIT unit[4]   = { LyXLength::UNIT_NONE,
47                               LyXLength::UNIT_NONE,
48                               LyXLength::UNIT_NONE,
49                               LyXLength::UNIT_NONE };
50 int number_index;
51 int unit_index;
52
53
54 inline
55 void lyx_advance(string & data, unsigned int n)
56 {
57         data.erase(0, n);
58 }
59
60
61 inline
62 bool isEndOfData(string const & data)
63 {
64         return frontStrip(data).empty();
65 }
66
67
68 char nextToken(string & data)
69 {
70         data = frontStrip(data);
71         if (data.empty())
72                 return '\0';
73         else if (data[0] == '+') {
74                 lyx_advance(data, 1);
75                 return '+';
76         } else if (prefixIs(data, "plus")) {
77                 lyx_advance(data, 4);
78                 return '+';
79         } else if (data[0] == '-') {
80                 lyx_advance(data, 1);
81                 return '-';
82         } else if (prefixIs(data, "minus")) {
83                 lyx_advance(data, 5);
84                 return '-';
85         } else {
86                 string::size_type i = data.find_first_not_of("0123456789.");
87
88                 if (i != 0) {
89                         if (number_index > 3) return 'E';
90
91                         string buffer;
92                 
93                         // we have found some number
94                         if (i == string::npos) {
95                                 buffer = data;
96                                 i = data.size() + 1;
97                         } else
98                                 buffer = data.substr(0, i);
99
100                         lyx_advance(data, i);
101
102                         if (isStrDbl(buffer)) {
103                                 number[number_index] = strToDbl(buffer);
104                                 ++number_index;
105                                 return 'n';
106                         } else return 'E';
107                 }
108                 
109                 i = data.find_first_not_of("abcdefghijklmnopqrstuvwxyz%");
110                 if (i != 0) {
111                         if (unit_index > 3) return 'E';
112
113                         string buffer;
114                 
115                         // we have found some alphabetical string
116                         if (i == string::npos) {
117                                 buffer = data;
118                                 i = data.size() + 1;
119                         } else
120                                 buffer = data.substr(0, i);
121
122                         // possibly we have "mmplus" string or similar
123                         if (buffer.size() > 5 && (buffer.substr(2,4) == string("plus") || buffer.substr(2,5) == string("minus"))) {
124                                 lyx_advance(data, 2);
125                                 unit[unit_index] = unitFromString(buffer.substr(0, 2));
126                         } else {
127                                 lyx_advance(data, i);
128                                 unit[unit_index] = unitFromString(buffer);
129                         }
130
131                         if (unit[unit_index] != LyXLength::UNIT_NONE) {
132                                 ++unit_index;
133                                 return 'u';
134                         } else return 'E';  // Error
135                 }
136                 return 'E';  // Error
137         }
138 }
139
140
141 struct LaTeXLength {
142         char const * pattern;
143         int   plus_val_index, minus_val_index,
144                 plus_uni_index, minus_uni_index;
145 };
146
147
148 LaTeXLength table[] = {
149         { "nu",       0, 0, 0, 0 },
150         { "nu+nu",    2, 0, 2, 0 },
151         { "nu+nu-nu", 2, 3, 2, 3 },
152         { "nu+-nu",   2, 2, 2, 2 },
153         { "nu-nu",    0, 2, 0, 2 },
154         { "nu-nu+nu", 3, 2, 3, 2 },
155         { "nu-+nu",   2, 2, 2, 2 },
156         { "n+nu",     2, 0, 1, 0 },
157         { "n+n-nu",   2, 3, 1, 1 },
158         { "n+-nu",    2, 2, 1, 1 },
159         { "n-nu",     0, 2, 0, 1 },
160         { "n-n+nu",   3, 2, 1, 1 },
161         { "n-+nu",    2, 2, 1, 1 },
162         { "",         0, 0, 0, 0 }   // sentinel, must be empty
163 };
164
165 } // namespace anon
166
167 const char * stringFromUnit(int unit)
168 {
169     if (unit < 0 || unit >= num_units)
170         return 0;
171     return unit_name[unit];
172 }
173
174
175 LyXLength::UNIT unitFromString (string const & data)
176 {
177         int i = 0;
178         while ((i < num_units) && (data != unit_name[i])) ++i;
179         return static_cast<LyXLength::UNIT>(i);
180 }
181
182
183 bool isValidGlueLength (string const & data, LyXGlueLength * result)
184 {
185         // This parser is table-driven.  First, it constructs a "pattern"
186         // that describes the sequence of tokens in "data".  For example,
187         // "n-nu" means: number, minus sign, number, unit.  As we go along,
188         // numbers and units are stored into static arrays.  Then, "pattern"
189         // is searched in the "table".  If it is found, the associated
190         // table entries tell us which number and unit should go where
191         // in the LyXLength structure.  Example: if "data" has the "pattern"
192         // "nu+nu-nu", the associated table entries are "2, 3, 2, 3".  
193         // That means, "plus_val" is the second number that was seen
194         // in the input, "minus_val" is the third number, and "plus_uni"
195         // and "minus_uni" are the second and third units, respectively.
196         // ("val" and "uni" are always the first items seen in "data".)
197         //     This is the most elegant solution I could find -- a straight-
198         // forward approach leads to very long, tedious code that would be
199         // much harder to understand and maintain. (AS)
200
201         if (data.empty())
202                 return true;
203         string buffer = frontStrip(data);
204
205         // To make isValidGlueLength recognize negative values as
206         // the first number this little hack is needed:
207         short val_sign = 1; // positive as default
208         switch (buffer[0]) {
209         case '-':
210                 lyx_advance(buffer, 1);
211                 val_sign = -1;
212                 break;
213         case '+':
214                 lyx_advance(buffer, 1);
215                 // fall through
216         default:
217                 // no action
218                 break;
219         }
220         // end of hack
221         
222         int       pattern_index = 0, table_index = 0;
223         char      pattern[20];
224
225         number_index = unit_index = 1;  // entries at index 0 are sentinels
226
227         // construct "pattern" from "data"
228         while (!isEndOfData (buffer)) {
229                 if (pattern_index > 20) return false;
230                 pattern[pattern_index] = nextToken (buffer);
231                 if (pattern[pattern_index] == 'E') return false;
232                 ++pattern_index;
233         }
234         pattern[pattern_index] = '\0';
235
236         // search "pattern" in "table"
237         table_index = 0;
238         while (compare(pattern, table[table_index].pattern)) {
239                 ++table_index;
240                 if (!*table[table_index].pattern) return false;
241         }
242         
243         // Get the values from the appropriate places.  If an index
244         // is zero, the corresponding array value is zero or UNIT_NONE,
245         // so we needn't check this.
246         if (result) {
247                 result->val = number[1] * val_sign;
248                 result->uni = unit[1];
249                 result->plus_val  = number[table[table_index].plus_val_index];
250                 result->minus_val = number[table[table_index].minus_val_index];
251                 result->plus_uni  = unit  [table[table_index].plus_uni_index];
252                 result->minus_uni = unit  [table[table_index].minus_uni_index];
253         }
254         return true;
255 }
256
257
258 bool isValidLength(string const & data, LyXLength * result)
259 {
260         /// This is a trimmed down version of isValidGlueLength.
261         /// The parser may seem overkill for lengths without 
262         /// glue, but since we already have it, using it is
263         /// easier than writing something from scratch.
264         if (data.empty())
265                 return true;
266
267         string   buffer(data);
268         int       pattern_index = 0;
269         char      pattern[3];
270
271         // To make isValidLength recognize negative values
272         // this little hack is needed:
273         short val_sign = 1; // positive as default
274         switch (buffer[0]) {
275         case '-':
276                 lyx_advance(buffer, 1);
277                 val_sign = -1;
278                 break;
279         case '+':
280                 lyx_advance(buffer, 1);
281                 // fall through
282         default:
283                 // no action
284                 break;
285         }
286         // end of hack
287         
288         number_index = unit_index = 1;  // entries at index 0 are sentinels
289
290         // construct "pattern" from "data"
291         while (!isEndOfData (buffer)) {
292                 if (pattern_index > 2) return false;
293                 pattern[pattern_index] = nextToken (buffer);
294                 if (pattern[pattern_index] == 'E') 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->uni = unit[1];
307         }
308         return true;
309 }
310
311
312 /// LyXLength class
313
314 LyXLength::LyXLength(string const & data)
315 {
316         LyXLength tmp;
317         
318         if (!isValidLength (data, &tmp))
319                 return; // should raise an exception
320         else {
321                 val = tmp.val;
322                 uni = tmp.uni;
323         }
324 }
325
326
327 string const LyXLength::asString() const
328 {
329         std::ostringstream buffer;
330         buffer << val << unit_name[uni]; // setw?
331         return buffer.str().c_str();
332 }
333
334
335 string const LyXLength::asLatexString() const
336 {
337         std::ostringstream buffer;
338         switch(uni) {
339         case PW:
340         case PE:
341             buffer << abs(static_cast<int>(val/100)) << "." << abs(static_cast<int>(val)%100) << "\\columnwidth";
342             break;
343         case PP:
344             buffer << "." << abs(static_cast<int>(val/100)) << "." << abs(static_cast<int>(val)%100) << "\\pagewidth";
345             break;
346         case PL:
347             buffer << "." << abs(static_cast<int>(val/100)) << "." << abs(static_cast<int>(val)%100) << "\\linewidth";
348             break;
349         default:
350             buffer << val << unit_name[uni]; // setw?
351             break;
352         }
353         return buffer.str().c_str();
354 }
355
356
357 /*  LyXGlueLength class
358  */
359
360 LyXGlueLength::LyXGlueLength (string const & data)
361 {
362         LyXGlueLength tmp(0.0, PT);
363
364         if (!isValidGlueLength (data, &tmp))
365                 return; // should raise an exception
366         else {
367                 val = tmp.val;
368                 uni = tmp.uni;
369                 plus_val = tmp.plus_val;
370                 plus_uni = tmp.plus_uni;
371                 minus_val = tmp.minus_val;
372                 minus_uni = tmp.minus_uni;
373         }
374 }
375
376
377 string const LyXGlueLength::asString() const
378 {
379         std::ostringstream buffer;
380
381         if (plus_val != 0.0)
382                 if (minus_val != 0.0)
383                         if ((uni == plus_uni) && (uni == minus_uni))
384                                 if (plus_val == minus_val)
385                                         buffer << val << "+-"
386                                                << plus_val << unit_name[uni];
387                                 else
388                                         buffer << val
389                                                << '+' << plus_val
390                                                << '-' << minus_val
391                                                << unit_name[uni];
392                         else
393                                 if (plus_uni == minus_uni
394                                     && plus_val == minus_val)
395                                         buffer << val << unit_name[uni]
396                                                << "+-" << plus_val
397                                                << unit_name[plus_uni];
398         
399                                 else
400                                         buffer << val << unit_name[uni]
401                                                << '+' << plus_val
402                                                << unit_name[plus_uni]
403                                                << '-' << minus_val
404                                                << unit_name[minus_uni];
405                 else 
406                         if (uni == plus_uni)
407                                 buffer << val << '+' << plus_val
408                                        << unit_name[uni];
409                         else
410                                 buffer << val << unit_name[uni]
411                                        << '+' << plus_val
412                                        << unit_name[plus_uni];
413         
414         else
415                 if (minus_val != 0.0)
416                         if (uni == minus_uni)
417                                 buffer << val << '-' << minus_val
418                                        << unit_name[uni];
419         
420                         else
421                                 buffer << val << unit_name[uni]
422                                        << '-' << minus_val
423                                        << unit_name[minus_uni];
424                 else
425                         buffer << val << unit_name[uni];
426
427         return buffer.str().c_str();
428 }
429
430
431 string const LyXGlueLength::asLatexString() const
432 {
433         std::ostringstream buffer;
434
435         if (plus_val != 0.0)
436                 if (minus_val != 0.0)
437                         buffer << val << unit_name[uni]
438                                << " plus "
439                                << plus_val << unit_name[plus_uni]
440                                << " minus "
441                                << minus_val << unit_name[minus_uni];
442                 else
443                         buffer << val << unit_name[uni]
444                                << " plus "
445                                << plus_val << unit_name[plus_uni];
446         else
447                 if (minus_val != 0.0)
448                         buffer << val << unit_name[uni]
449                                << " minus "
450                                << minus_val << unit_name[minus_uni];
451                 else
452                         buffer << val << unit_name[uni];
453
454         return buffer.str().c_str();
455 }
456
457
458 /*  VSpace class
459  */
460
461 VSpace::VSpace (string const & data)
462         : kin (NONE), len (0.0, LyXLength::PT) 
463 {
464         kp = false;
465         if (data.empty())
466                 return;
467         float   value;
468         string input  = strip(data);
469
470         int length = input.length();
471
472         if (length > 1 && input[length-1] == '*') {
473                 kp = true;
474                 input.erase(length - 1);
475         }
476
477         if      (prefixIs (input, "defskip"))   kin = DEFSKIP;
478         else if (prefixIs (input, "smallskip")) kin = SMALLSKIP;
479         else if (prefixIs (input, "medskip"))   kin = MEDSKIP;
480         else if (prefixIs (input, "bigskip"))   kin = BIGSKIP;
481         else if (prefixIs (input, "vfill"))     kin = VFILL;
482         else if (isValidGlueLength (input, &len))
483                 kin = LENGTH;
484         else if (sscanf(input.c_str(), "%f", &value) == 1) {
485                 // This last one is for reading old .lyx files
486                 // without units in added_space_top/bottom.
487                 // Let unit default to centimeters here.
488                 kin = LENGTH;
489                 len = LyXGlueLength (value, LyXLength::CM);
490         }
491 }
492
493
494 bool VSpace::operator==(VSpace const & other) const
495 {
496         if (this->kin != other.kin)
497                 return false;
498  
499         if (this->kin != LENGTH)
500                 return this->kp == other.kp;
501  
502         if (!(this->len == other.len))
503                 return false;
504  
505         return this->kp == other.kp;
506 }
507
508
509 string const VSpace::asLyXCommand() const
510 {
511         string result;
512
513         switch (kin) {
514         case NONE:      break;
515         case DEFSKIP:   result = "defskip";      break;
516         case SMALLSKIP: result = "smallskip";    break;
517         case MEDSKIP:   result = "medskip";      break;
518         case BIGSKIP:   result = "bigskip";      break;
519         case VFILL:     result = "vfill";        break;
520         case LENGTH:    result = len.asString(); break;
521         }
522         if (kp && (kin != NONE) && (kin != DEFSKIP))
523                 return result += '*';
524         else
525                 return result;
526 }
527
528
529 string const VSpace::asLatexCommand(BufferParams const & params) const
530 {
531         switch (kin) {
532         case NONE:      return string();
533         case DEFSKIP:   
534                 return params.getDefSkip().asLatexCommand(params);
535         case SMALLSKIP: return kp ? "\\vspace*{\\smallskipamount}"
536                                 : "\\smallskip{}";
537         case MEDSKIP:   return kp ? "\\vspace*{\\medskipamount}"
538                                 : "\\medskip{}";
539         case BIGSKIP:   return kp ? "\\vspace*{\\bigskipamount}"
540                                 : "\\bigskip{}";
541         case VFILL:     return kp ? "\\vspace*{\\fill}"
542                                 : "\\vfill{}";
543         case LENGTH:    return kp ? "\\vspace*{" + len.asLatexString() + '}'
544                                 : "\\vspace{" + len.asLatexString() + '}';
545         }
546         return string();  // should never be reached
547 }
548
549
550 int VSpace::inPixels(BufferView * bv) const
551 {
552         // Height of a normal line in pixels (zoom factor considered)
553         int height = bv->text->DefaultHeight(); // [pixels]
554         int skip = 0;
555         int width = bv->workWidth();
556
557         if (kin == DEFSKIP)
558             skip = bv->buffer()->params.getDefSkip().inPixels(bv);
559
560         return inPixels(height, skip, width);
561 }
562
563 int VSpace::inPixels(int default_height, int default_skip, int default_width) const
564 {
565         // Height of a normal line in pixels (zoom factor considered)
566         int height = default_height; // [pixels]
567         
568         // Zoom factor specified by user in percent
569         float const zoom = lyxrc.zoom / 100.0; // [percent]
570
571         // DPI setting for monitor: pixels/inch
572         float const dpi = lyxrc.dpi; // screen resolution [pixels/inch]
573
574         // We want the result in pixels
575         float result, value;
576
577         switch (kin) {
578         case NONE: return 0;
579
580         case DEFSKIP:
581                 return default_skip;
582
583                 // This is how the skips are normally defined by
584                 // LateX.  But there should be some way to change
585                 // this per document.
586         case SMALLSKIP: return height / 4;
587         case MEDSKIP:   return height / 2;
588         case BIGSKIP:   return height;
589         case VFILL:     return 3 * height;
590                 // leave space for the vfill symbol
591         case LENGTH:
592                 // Pixel values are scaled so that the ratio
593                 // between lengths and font sizes on the screen
594                 // is the same as on paper.
595
596                 // we don't care about sign of value, we
597                 // can't display negative anyway
598                 result = 0.0;
599                 value = len.value();
600                 short val_sign = value < 0.0 ? -1 : 1;
601                 
602                 switch (len.unit()) {
603                 case LyXLength::SP:
604                         // Scaled point: sp = 1/65536 pt
605                         result = zoom * dpi * value
606                                 / (72.27 * 65536); // 4736286.7
607                         break;
608                 case LyXLength::PT:
609                         // Point: 1 pt = 1/72.27 inch
610                         result = zoom * dpi * value
611                                 / 72.27; // 72.27
612                         break;
613                 case LyXLength::BP:
614                         // Big point: 1 bp = 1/72 inch
615                         result = zoom * dpi * value
616                                 / 72; // 72
617                         break;
618                 case LyXLength::DD:
619                         // Didot: 1157dd = 1238 pt?
620                         result = zoom * dpi * value
621                                 / (72.27 / (0.376 * 2.845)); // 67.559735
622                         break;
623                 case LyXLength::MM:
624                         // Millimeter: 1 mm = 1/25.4 inch
625                         result = zoom * dpi * value
626                                 / 25.4; // 25.4
627                         break;
628                 case LyXLength::PC:
629                         // Pica: 1 pc = 12 pt
630                         result = zoom * dpi * value
631                                 / (72.27 / 12); // 6.0225
632                         break;
633                 case LyXLength::CC:
634                         // Cicero: 1 cc = 12 dd
635                         result = zoom * dpi * value
636                                 / (72.27 / (12 * 0.376 * 2.845)); // 5.6299779
637                         break;
638                 case LyXLength::CM:
639                         // Centimeter: 1 cm = 1/2.54 inch
640                         result = zoom * dpi * value
641                                 / 2.54; // 2.54
642                         break;
643                 case LyXLength::IN:
644                         // Inch
645                         result = zoom * dpi * value;
646                         break;
647                 case LyXLength::EX:
648                         // Ex: The height of an "x"
649                         result = zoom * value * height / 2; // what to / width?
650                         break;
651                 case LyXLength::EM: // what to / width?
652                         // Em: The width of an "m"
653                         result = zoom * value * height / 2; // Why 2?
654                         break;
655                 case LyXLength::MU: // This is probably only allowed in
656                         // math mode
657                         result = zoom * value * height;
658                         break;
659                 case LyXLength::PW: // Always % of workarea
660                 case LyXLength::PE:
661                 case LyXLength::PP:
662                 case LyXLength::PL:
663                         result = value * default_width / 100;
664                         break;
665                 case LyXLength::UNIT_NONE:
666                         result = 0;  // this cannot happen
667                         break;  
668                 }
669                 return int (result * val_sign + 0.5);
670         }
671         return 0; // never reached
672 }