1 // -*- C++ -*-
2 /* Copyright (C) 1989-2018 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
vjustify(vunits,symbol)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();
is_active()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
type()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
output_line()145 output_line::output_line() : next(0)
146 {
147 }
148
~output_line()149 output_line::~output_line()
150 {
151 }
152
reset()153 void output_line::reset()
154 {
155 }
156
as_transparent_output_line()157 transparent_output_line *output_line::as_transparent_output_line()
158 {
159 return 0;
160 }
161
as_vjustify_output_line()162 vjustify_output_line *output_line::as_vjustify_output_line()
163 {
164 return 0;
165 }
166
output(output_file *,vunits)167 void output_line::output(output_file *, vunits)
168 {
169 }
170
distance()171 vunits output_line::distance()
172 {
173 return V0;
174 }
175
height()176 vunits output_line::height()
177 {
178 return V0;
179 }
180
extra_space()181 vunits output_line::extra_space()
182 {
183 return V0;
184 }
185
position_output_line(vunits d)186 position_output_line::position_output_line(vunits d)
187 : dist(d)
188 {
189 }
190
distance()191 vunits position_output_line::distance()
192 {
193 return dist;
194 }
195
node_output_line(vunits d,node * n,hunits po,vunits b,vunits a)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
~node_output_line()201 node_output_line::~node_output_line()
202 {
203 delete_node_list(nd);
204 }
205
output(output_file * out,vunits pos)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
height()212 vunits node_output_line::height()
213 {
214 return after;
215 }
216
extra_space()217 vunits node_output_line::extra_space()
218 {
219 return after;
220 }
221
vjustify_output_line(vunits d,symbol t)222 vjustify_output_line::vjustify_output_line(vunits d, symbol t)
223 : position_output_line(d), typ(t)
224 {
225 }
226
reset()227 void vjustify_output_line::reset()
228 {
229 current = V0;
230 }
231
height()232 vunits vjustify_output_line::height()
233 {
234 return current;
235 }
236
as_vjustify_output_line()237 vjustify_output_line *vjustify_output_line::as_vjustify_output_line()
238 {
239 return this;
240 }
241
vary(vunits amount)242 inline void vjustify_output_line::vary(vunits amount)
243 {
244 current += amount;
245 }
246
transparent_output_line()247 transparent_output_line::transparent_output_line()
248 {
249 }
250
as_transparent_output_line()251 transparent_output_line *transparent_output_line::as_transparent_output_line()
252 {
253 return this;
254 }
255
append_char(unsigned char c)256 void transparent_output_line::append_char(unsigned char c)
257 {
258 assert(c != 0);
259 buf += c;
260 }
261
output(output_file * out,vunits)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
copy_file_output_line(vunits d,const char * f,hunits h)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
output(output_file * out,vunits pos)274 void copy_file_output_line::output(output_file *out, vunits pos)
275 {
276 out->copy_file(hpos, pos, filename.contents());
277 }
278
column()279 column::column()
280 : bottom(V0), col(0), tail(&col), out(0)
281 {
282 }
283
~column()284 column::~column()
285 {
286 assert(out != 0);
287 error("automatically outputting column before exiting");
288 output();
289 delete the_output;
290 }
291
start()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
begin_page(int pageno,vunits page_length)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
flush()315 void column::flush()
316 {
317 assert(out != 0);
318 out->flush();
319 }
320
is_printing()321 int column::is_printing()
322 {
323 assert(out != 0);
324 return out->is_printing();
325 }
326
get_bottom()327 vunits column::get_bottom()
328 {
329 return bottom;
330 }
331
add_output_line(output_line * ln)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
print_line(hunits page_offset,vunits pos,node * nd,vunits before,vunits after)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
vjustify(vunits pos,symbol typ)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
transparent_char(unsigned char c)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
copy_file(hunits page_offset,vunits pos,const char * filename)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
trim()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
reset()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
check_bottom()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
output()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
get_last_extra_space()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
justification_spec(vunits h)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
~justification_spec()464 justification_spec::~justification_spec()
465 {
466 a_delete type;
467 a_delete amount;
468 }
469
append(symbol t,vunits v)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
justify(output_line * col,vunits * bottomp) const501 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
justify(const justification_spec & js)543 void column::justify(const justification_spec &js)
544 {
545 check_bottom();
546 js.justify(col, &bottom);
547 check_bottom();
548 }
549
column_justify()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
column_start()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
column_output()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
column_trim()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
column_reset()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
get_string()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
get_string()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
get_string()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
vjustify_node(symbol t)661 vjustify_node::vjustify_node(symbol t)
662 : typ(t)
663 {
664 }
665
copy()666 node *vjustify_node::copy()
667 {
668 return new vjustify_node(typ, div_nest_level);
669 }
670
type()671 const char *vjustify_node::type()
672 {
673 return "vjustify_node";
674 }
675
same(node * nd)676 int vjustify_node::same(node *nd)
677 {
678 return typ == ((vjustify_node *)nd)->typ;
679 }
680
reread(int * bolp)681 int vjustify_node::reread(int *bolp)
682 {
683 curdiv->vjustify(typ);
684 *bolp = 1;
685 return 1;
686 }
687
vjustify(symbol type)688 void macro_diversion::vjustify(symbol type)
689 {
690 if (!no_vjustify_mode)
691 mac->append(new vjustify_node(type));
692 }
693
vjustify(symbol type)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
no_vjustify()702 void no_vjustify()
703 {
704 skip_line();
705 no_vjustify_mode = 1;
706 }
707
restore_vjustify()708 void restore_vjustify()
709 {
710 skip_line();
711 no_vjustify_mode = 0;
712 }
713
init_column_requests()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 */
732