20ed3f9456106c3ba85e1cb7c729c1c4eee0fcad
[platform/upstream/groff.git] / src / roff / troff / column.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-2014  Free Software Foundation, Inc.
3      Written by James Clark (jjc@jclark.com)
4
5 This file is part of groff.
6
7 groff is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 groff is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19
20 #ifdef COLUMN
21
22 #include "troff.h"
23 #include "symbol.h"
24 #include "dictionary.h"
25 #include "hvunits.h"
26 #include "env.h"
27 #include "request.h"
28 #include "node.h"
29 #include "token.h"
30 #include "div.h"
31 #include "reg.h"
32 #include "stringclass.h"
33
34 void output_file::vjustify(vunits, symbol)
35 {
36   // do nothing
37 }
38
39 struct justification_spec;
40 struct output_line;
41
42 class column : public output_file {
43 private:
44   output_file *out;
45   vunits bottom;
46   output_line *col;
47   output_line **tail;
48   void add_output_line(output_line *);
49   void begin_page(int pageno, vunits page_length);
50   void flush();
51   void print_line(hunits, vunits, node *, vunits, vunits);
52   void vjustify(vunits, symbol);
53   void transparent_char(unsigned char c);
54   void copy_file(hunits, vunits, const char *);
55   int is_printing();
56   void check_bottom();
57 public:
58   column();
59   ~column();
60   void start();
61   void output();
62   void justify(const justification_spec &);
63   void trim();
64   void reset();
65   vunits get_bottom();
66   vunits get_last_extra_space();
67   int is_active() { return out != 0; }
68 };
69
70 column *the_column = 0;
71
72 struct transparent_output_line;
73 struct vjustify_output_line;
74
75 class output_line {
76   output_line *next;
77 public:
78   output_line();
79   virtual ~output_line();
80   virtual void output(output_file *, vunits);
81   virtual transparent_output_line *as_transparent_output_line();
82   virtual vjustify_output_line *as_vjustify_output_line();
83   virtual vunits distance();
84   virtual vunits height();
85   virtual void reset();
86   virtual vunits extra_space(); // post line
87   friend class column;
88   friend class justification_spec;
89 };
90
91 class position_output_line : public output_line {
92   vunits dist;
93 public:
94   position_output_line(vunits);
95   vunits distance();
96 };
97   
98 class node_output_line : public position_output_line {
99   node *nd;
100   hunits page_offset;
101   vunits before;
102   vunits after;
103 public:
104   node_output_line(vunits, node *, hunits, vunits, vunits);
105   ~node_output_line();
106   void output(output_file *, vunits);
107   vunits height();
108   vunits extra_space();
109 };
110
111 class vjustify_output_line : public position_output_line {
112   vunits current;
113   symbol typ;
114 public:
115   vjustify_output_line(vunits dist, symbol);
116   vunits height();
117   vjustify_output_line *as_vjustify_output_line();
118   void vary(vunits amount);
119   void reset();
120   symbol type();
121 };
122
123 inline symbol vjustify_output_line::type()
124 {
125   return typ;
126 }
127
128 class copy_file_output_line : public position_output_line {
129   symbol filename;
130   hunits hpos;
131 public:
132   copy_file_output_line(vunits, const char *, hunits);
133   void output(output_file *, vunits);
134 };
135
136 class transparent_output_line : public output_line {
137   string buf;
138 public:
139   transparent_output_line();
140   void output(output_file *, vunits);
141   void append_char(unsigned char c);
142   transparent_output_line *as_transparent_output_line();
143 };
144
145 output_line::output_line() : next(0)
146 {
147 }
148
149 output_line::~output_line()
150 {
151 }
152
153 void output_line::reset()
154 {
155 }
156
157 transparent_output_line *output_line::as_transparent_output_line()
158 {
159   return 0;
160 }
161
162 vjustify_output_line *output_line::as_vjustify_output_line()
163 {
164   return 0;
165 }
166
167 void output_line::output(output_file *, vunits)
168 {
169 }
170
171 vunits output_line::distance()
172 {
173   return V0;
174 }
175
176 vunits output_line::height()
177 {
178   return V0;
179 }
180
181 vunits output_line::extra_space()
182 {
183   return V0;
184 }
185
186 position_output_line::position_output_line(vunits d)
187 : dist(d)
188 {
189 }
190
191 vunits position_output_line::distance()
192 {
193   return dist;
194 }
195
196 node_output_line::node_output_line(vunits d, node *n, hunits po, vunits b, vunits a)
197 : position_output_line(d), nd(n), page_offset(po), before(b), after(a)
198 {
199 }
200
201 node_output_line::~node_output_line()
202 {
203   delete_node_list(nd);
204 }
205
206 void node_output_line::output(output_file *out, vunits pos)
207 {
208   out->print_line(page_offset, pos, nd, before, after);
209   nd = 0;
210 }
211
212 vunits node_output_line::height()
213 {
214   return after;
215 }
216
217 vunits node_output_line::extra_space()
218 {
219   return after;
220 }
221
222 vjustify_output_line::vjustify_output_line(vunits d, symbol t)
223 : position_output_line(d), typ(t)
224 {
225 }
226
227 void vjustify_output_line::reset()
228 {
229   current = V0;
230 }
231
232 vunits vjustify_output_line::height()
233 {
234   return current;
235 }
236
237 vjustify_output_line *vjustify_output_line::as_vjustify_output_line()
238 {
239   return this;
240 }
241
242 inline void vjustify_output_line::vary(vunits amount)
243 {
244   current += amount;
245 }
246
247 transparent_output_line::transparent_output_line()
248 {
249 }
250
251 transparent_output_line *transparent_output_line::as_transparent_output_line()
252 {
253   return this;
254 }
255
256 void transparent_output_line::append_char(unsigned char c)
257 {
258   assert(c != 0);
259   buf += c;
260 }
261
262 void transparent_output_line::output(output_file *out, vunits)
263 {
264   int len = buf.length();
265   for (int i = 0; i < len; i++)
266     out->transparent_char(buf[i]);
267 }
268
269 copy_file_output_line::copy_file_output_line(vunits d, const char *f, hunits h)
270 : position_output_line(d), hpos(h), filename(f)
271 {
272 }
273
274 void copy_file_output_line::output(output_file *out, vunits pos)
275 {
276   out->copy_file(hpos, pos, filename.contents());
277 }
278
279 column::column()
280 : bottom(V0), col(0), tail(&col), out(0)
281 {
282 }
283
284 column::~column()
285 {
286   assert(out != 0);
287   error("automatically outputting column before exiting");
288   output();
289   delete the_output;
290 }
291
292 void column::start()
293 {
294   assert(out == 0);
295   if (!the_output)
296     init_output();
297   assert(the_output != 0);
298   out = the_output;
299   the_output = this;
300 }
301
302 void column::begin_page(int pageno, vunits page_length)
303 {
304   assert(out != 0);
305   if (col) {
306     error("automatically outputting column before beginning next page");
307     output();
308     the_output->begin_page(pageno, page_length);
309   }
310   else
311     out->begin_page(pageno, page_length);
312     
313 }
314
315 void column::flush()
316 {
317   assert(out != 0);
318   out->flush();
319 }
320
321 int column::is_printing()
322 {
323   assert(out != 0);
324   return out->is_printing();
325 }
326
327 vunits column::get_bottom()
328 {
329   return bottom;
330 }
331
332 void column::add_output_line(output_line *ln)
333 {
334   *tail = ln;
335   bottom += ln->distance();
336   bottom += ln->height();
337   ln->next = 0;
338   tail = &(*tail)->next;
339 }
340
341 void column::print_line(hunits page_offset, vunits pos, node *nd,
342                         vunits before, vunits after)
343 {
344   assert(out != 0);
345   add_output_line(new node_output_line(pos - bottom, nd, page_offset, before, after));
346 }
347
348 void column::vjustify(vunits pos, symbol typ)
349 {
350   assert(out != 0);
351   add_output_line(new vjustify_output_line(pos - bottom, typ));
352 }
353
354 void column::transparent_char(unsigned char c)
355 {
356   assert(out != 0);
357   transparent_output_line *tl = 0;
358   if (*tail)
359     tl = (*tail)->as_transparent_output_line();
360   if (!tl) {
361     tl = new transparent_output_line;
362     add_output_line(tl);
363   }
364   tl->append_char(c);
365 }
366
367 void column::copy_file(hunits page_offset, vunits pos, const char *filename)
368 {
369   assert(out != 0);
370   add_output_line(new copy_file_output_line(pos - bottom, filename, page_offset));
371 }
372
373 void column::trim()
374 {
375   output_line **spp = 0;
376   for (output_line **pp = &col; *pp; pp = &(*pp)->next)
377     if ((*pp)->as_vjustify_output_line() == 0)
378       spp = 0;
379     else if (!spp)
380       spp = pp;
381   if (spp) {
382     output_line *ln = *spp;
383     *spp = 0;
384     tail = spp;
385     while (ln) {
386       output_line *tem = ln->next;
387       bottom -= ln->distance();
388       bottom -= ln->height();
389       delete ln;
390       ln = tem;
391     }
392   }
393 }
394
395 void column::reset()
396 {
397   bottom = V0;
398   for (output_line *ln = col; ln; ln = ln->next) {
399     bottom += ln->distance();
400     ln->reset();
401     bottom += ln->height();
402   }
403 }
404
405 void column::check_bottom()
406 {
407   vunits b;
408   for (output_line *ln = col; ln; ln = ln->next) {
409     b += ln->distance();
410     b += ln->height();
411   }
412   assert(b == bottom);
413 }
414
415 void column::output()
416 {
417   assert(out != 0);
418   vunits vpos(V0);
419   output_line *ln = col;
420   while (ln) {
421     vpos += ln->distance();
422     ln->output(out, vpos);
423     vpos += ln->height();
424     output_line *tem = ln->next;
425     delete ln;
426     ln = tem;
427   }
428   tail = &col;
429   bottom = V0;
430   col = 0;
431   the_output = out;
432   out = 0;
433 }
434
435 vunits column::get_last_extra_space()
436 {
437   if (!col)
438     return V0;
439   for (output_line *p = col; p->next; p = p->next)
440     ;
441   return p->extra_space();
442 }
443
444 class justification_spec {
445   vunits height;
446   symbol *type;
447   vunits *amount;
448   int n;
449   int maxn;
450 public:
451   justification_spec(vunits);
452   ~justification_spec();
453   void append(symbol t, vunits v);
454   void justify(output_line *, vunits *bottomp) const;
455 };
456
457 justification_spec::justification_spec(vunits h)
458 : height(h), n(0), maxn(10)
459 {
460   type = new symbol[maxn];
461   amount = new vunits[maxn];
462 }
463
464 justification_spec::~justification_spec()
465 {
466   a_delete type;
467   a_delete amount;
468 }
469
470 void justification_spec::append(symbol t, vunits v)
471 {
472   if (v <= V0) {
473     if (v < V0)
474       warning(WARN_RANGE,
475               "maximum space for vertical justification must not be negative");
476     else
477       warning(WARN_RANGE,
478               "maximum space for vertical justification must not be zero");
479     return;
480   }
481   if (n >= maxn) {
482     maxn *= 2;
483     symbol *old_type = type;
484     type = new symbol[maxn];
485     int i;
486     for (i = 0; i < n; i++)
487       type[i] = old_type[i];
488     a_delete old_type;
489     vunits *old_amount = amount;
490     amount = new vunits[maxn];
491     for (i = 0; i < n; i++)
492       amount[i] = old_amount[i];
493     a_delete old_amount;
494   }
495   assert(n < maxn);
496   type[n] = t;
497   amount[n] = v;
498   n++;
499 }
500
501 void justification_spec::justify(output_line *col, vunits *bottomp) const
502 {
503   if (*bottomp >= height)
504     return;
505   vunits total;
506   output_line *p;
507   for (p = col; p; p = p->next) {
508     vjustify_output_line *sp = p->as_vjustify_output_line();
509     if (sp) {
510       symbol t = sp->type();
511       for (int i = 0; i < n; i++) {
512         if (t == type[i])
513           total += amount[i];
514       }
515     }
516   }
517   vunits gap = height - *bottomp;
518   for (p = col; p; p = p->next) {
519     vjustify_output_line *sp = p->as_vjustify_output_line();
520     if (sp) {
521       symbol t = sp->type();
522       for (int i = 0; i < n; i++) {
523         if (t == type[i]) {
524           if (total <= gap) {
525             sp->vary(amount[i]);
526             gap -= amount[i];
527           }
528           else {
529             // gap < total
530             vunits v = scale(amount[i], gap, total);
531             sp->vary(v);
532             gap -= v;
533           }
534           total -= amount[i];
535         }
536       }
537     }
538   }
539   assert(total == V0);
540   *bottomp = height - gap;
541 }
542   
543 void column::justify(const justification_spec &js)
544 {
545   check_bottom();
546   js.justify(col, &bottom);
547   check_bottom();
548 }
549
550 void column_justify()
551 {
552   vunits height;
553   if (!the_column->is_active())
554     error("can't justify column - column not active");
555   else if (get_vunits(&height, 'v')) {
556     justification_spec js(height);
557     symbol nm = get_long_name(1);
558     if (!nm.is_null()) {
559       vunits v;
560       if (get_vunits(&v, 'v')) {
561         js.append(nm, v);
562         int err = 0;
563         while (has_arg()) {
564           nm = get_long_name(1);
565           if (nm.is_null()) {
566             err = 1;
567             break;
568           }
569           if (!get_vunits(&v, 'v')) {
570             err = 1;
571             break;
572           }
573           js.append(nm, v);
574         }
575         if (!err)
576           the_column->justify(js);
577       }
578     }
579   }
580   skip_line();
581 }
582
583 void column_start()
584 {
585   if (the_column->is_active())
586     error("can't start column - column already active");
587   else
588     the_column->start();
589   skip_line();
590 }
591
592 void column_output()
593 {
594   if (!the_column->is_active())
595     error("can't output column - column not active");
596   else
597     the_column->output();
598   skip_line();
599 }
600
601 void column_trim()
602 {
603   if (!the_column->is_active())
604     error("can't trim column - column not active");
605   else
606     the_column->trim();
607   skip_line();
608 }
609
610 void column_reset()
611 {
612   if (!the_column->is_active())
613     error("can't reset column - column not active");
614   else
615     the_column->reset();
616   skip_line();
617 }
618
619 class column_bottom_reg : public reg {
620 public:
621   const char *get_string();
622 };
623
624 const char *column_bottom_reg::get_string()
625 {
626   return i_to_a(the_column->get_bottom().to_units());
627 }
628
629 class column_extra_space_reg : public reg {
630 public:
631   const char *get_string();
632 };
633
634 const char *column_extra_space_reg::get_string()
635 {
636   return i_to_a(the_column->get_last_extra_space().to_units());
637 }
638
639 class column_active_reg : public reg {
640 public:
641   const char *get_string();
642 };
643
644 const char *column_active_reg::get_string()
645 {
646   return the_column->is_active() ? "1" : "0";
647 }
648
649 static int no_vjustify_mode = 0;
650
651 class vjustify_node : public node {
652   symbol typ;
653 public:
654   vjustify_node(symbol);
655   int reread(int *);
656   const char *type();
657   int same(node *);
658   node *copy();
659 };
660
661 vjustify_node::vjustify_node(symbol t)
662 : typ(t)
663 {
664 }
665
666 node *vjustify_node::copy()
667 {
668   return new vjustify_node(typ, div_nest_level);
669 }
670
671 const char *vjustify_node::type()
672 {
673   return "vjustify_node";
674 }
675
676 int vjustify_node::same(node *nd)
677 {
678   return typ == ((vjustify_node *)nd)->typ;
679 }
680
681 int vjustify_node::reread(int *bolp)
682 {
683   curdiv->vjustify(typ);
684   *bolp = 1;
685   return 1;
686 }
687
688 void macro_diversion::vjustify(symbol type)
689 {
690   if (!no_vjustify_mode)
691     mac->append(new vjustify_node(type));
692 }
693
694 void top_level_diversion::vjustify(symbol type)
695 {
696   if (no_space_mode || no_vjustify_mode)
697     return;
698   assert(first_page_begun);     // I'm not sure about this.
699   the_output->vjustify(vertical_position, type);
700 }
701
702 void no_vjustify()
703 {
704   skip_line();
705   no_vjustify_mode = 1;
706 }
707
708 void restore_vjustify()
709 {
710   skip_line();
711   no_vjustify_mode = 0;
712 }
713
714 void init_column_requests()
715 {
716   the_column = new column;
717   init_request("cols", column_start);
718   init_request("colo", column_output);
719   init_request("colj", column_justify);
720   init_request("colr", column_reset);
721   init_request("colt", column_trim);
722   init_request("nvj", no_vjustify);
723   init_request("rvj", restore_vjustify);
724   number_reg_dictionary.define(".colb", new column_bottom_reg);
725   number_reg_dictionary.define(".colx", new column_extra_space_reg);
726   number_reg_dictionary.define(".cola", new column_active_reg);
727   number_reg_dictionary.define(".nvj",
728                                new constant_int_reg(&no_vjustify_mode));
729 }
730
731 #endif /* COLUMN */