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