155682Smarkm// -*- C++ -*-
2233294Sstas/* Copyright (C) 1989, 1990, 1991, 1992, 2002, 2004
3233294Sstas   Free Software Foundation, Inc.
4233294Sstas     Written by James Clark (jjc@jclark.com)
555682Smarkm
6233294SstasThis file is part of groff.
7233294Sstas
8233294Sstasgroff is free software; you can redistribute it and/or modify it under
955682Smarkmthe terms of the GNU General Public License as published by the Free
10233294SstasSoftware Foundation; either version 2, or (at your option) any later
11233294Sstasversion.
1255682Smarkm
13233294Sstasgroff is distributed in the hope that it will be useful, but WITHOUT ANY
14233294SstasWARRANTY; without even the implied warranty of MERCHANTABILITY or
15233294SstasFITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1655682Smarkmfor more details.
17233294Sstas
18233294SstasYou should have received a copy of the GNU General Public License along
19233294Sstaswith groff; see the file COPYING.  If not, write to the Free Software
2055682SmarkmFoundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
21233294Sstas
22233294Sstas#include "eqn.h"
23233294Sstas#include "pbox.h"
24233294Sstas
25233294Sstasconst char *current_roman_font;
26233294Sstas
27233294Sstaschar *gfont = 0;
28233294Sstaschar *grfont = 0;
29233294Sstaschar *gbfont = 0;
30233294Sstasint gsize = 0;
31233294Sstas
3255682Smarkmint script_size_reduction = -1;	// negative means reduce by a percentage
3355682Smarkm
3455682Smarkmint positive_space = -1;
3555682Smarkmint negative_space = -1;
3655682Smarkm
3755682Smarkmint minimum_size = 5;
3855682Smarkm
39120945Snectarint fat_offset = 4;
4055682Smarkmint body_height = 85;
4155682Smarkmint body_depth = 35;
4255682Smarkm
4355682Smarkmint over_hang = 0;
4455682Smarkmint accent_width = 31;
4555682Smarkmint delimiter_factor = 900;
4655682Smarkmint delimiter_shortfall = 50;
4755682Smarkm
4855682Smarkmint null_delimiter_space = 12;
4955682Smarkmint script_space = 5;
5055682Smarkmint thin_space = 17;
5155682Smarkmint medium_space = 22;
5255682Smarkmint thick_space = 28;
53233294Sstas
5455682Smarkmint num1 = 70;
5555682Smarkmint num2 = 40;
5655682Smarkm// we don't use num3, because we don't have \atop
5755682Smarkmint denom1 = 70;
5855682Smarkmint denom2 = 36;
59233294Sstasint axis_height = 26;		// in 100ths of an em
6055682Smarkmint sup1 = 42;
6155682Smarkmint sup2 = 37;
6255682Smarkmint sup3 = 28;
6355682Smarkmint default_rule_thickness = 4;
6455682Smarkmint sub1 = 20;
65233294Sstasint sub2 = 23;
6655682Smarkmint sup_drop = 38;
6755682Smarkmint sub_drop = 5;
6855682Smarkmint x_height = 45;
6955682Smarkmint big_op_spacing1 = 11;
7055682Smarkmint big_op_spacing2 = 17;
71233294Sstasint big_op_spacing3 = 20;
7255682Smarkmint big_op_spacing4 = 60;
7355682Smarkmint big_op_spacing5 = 10;
7455682Smarkm
7555682Smarkm// These are for piles and matrices.
76
77int baseline_sep = 140;		// = num1 + denom1
78int shift_down = 26;		// = axis_height
79int column_sep = 100;		// = em space
80int matrix_side_sep = 17;	// = thin space
81
82int nroff = 0;			// should we grok ndefine or tdefine?
83
84struct S {
85  const char *name;
86  int *ptr;
87} param_table[] = {
88  { "fat_offset", &fat_offset },
89  { "over_hang", &over_hang },
90  { "accent_width", &accent_width },
91  { "delimiter_factor", &delimiter_factor },
92  { "delimiter_shortfall", &delimiter_shortfall },
93  { "null_delimiter_space", &null_delimiter_space },
94  { "script_space", &script_space },
95  { "thin_space", &thin_space },
96  { "medium_space", &medium_space },
97  { "thick_space", &thick_space },
98  { "num1", &num1 },
99  { "num2", &num2 },
100  { "denom1", &denom1 },
101  { "denom2", &denom2 },
102  { "axis_height", &axis_height },
103  { "sup1", &sup1 },
104  { "sup2", &sup2 },
105  { "sup3", &sup3 },
106  { "default_rule_thickness", &default_rule_thickness },
107  { "sub1", &sub1 },
108  { "sub2", &sub2 },
109  { "sup_drop", &sup_drop },
110  { "sub_drop", &sub_drop },
111  { "x_height", &x_height },
112  { "big_op_spacing1", &big_op_spacing1 },
113  { "big_op_spacing2", &big_op_spacing2 },
114  { "big_op_spacing3", &big_op_spacing3 },
115  { "big_op_spacing4", &big_op_spacing4 },
116  { "big_op_spacing5", &big_op_spacing5 },
117  { "minimum_size", &minimum_size },
118  { "baseline_sep", &baseline_sep },
119  { "shift_down", &shift_down },
120  { "column_sep", &column_sep },
121  { "matrix_side_sep", &matrix_side_sep },
122  { "draw_lines", &draw_flag },
123  { "body_height", &body_height },
124  { "body_depth", &body_depth },
125  { "nroff", &nroff },
126  { 0, 0 }
127};
128
129void set_param(const char *name, int value)
130{
131  for (int i = 0; param_table[i].name != 0; i++)
132    if (strcmp(param_table[i].name, name) == 0) {
133      *param_table[i].ptr = value;
134      return;
135    }
136  error("unrecognised parameter `%1'", name);
137}
138
139int script_style(int style)
140{
141  return style > SCRIPT_STYLE ? style - 2 : style;
142}
143
144int cramped_style(int style)
145{
146  return (style & 1) ? style - 1 : style;
147}
148
149void set_space(int n)
150{
151  if (n < 0)
152    negative_space = -n;
153  else
154    positive_space = n;
155}
156
157// Return 0 if the specified size is bad.
158// The caller is responsible for giving the error message.
159
160int set_gsize(const char *s)
161{
162  const char *p = (*s == '+' || *s == '-') ? s + 1 : s;
163  char *end;
164  long n = strtol(p, &end, 10);
165  if (n <= 0 || *end != '\0' || n > INT_MAX)
166    return 0;
167  if (p > s) {
168    if (!gsize)
169      gsize = 10;
170    if (*s == '+') {
171      if (gsize > INT_MAX - n)
172	return 0;
173      gsize += int(n);
174    }
175    else {
176      if (gsize - n <= 0)
177	return 0;
178      gsize -= int(n);
179    }
180  }
181  else
182    gsize = int(n);
183  return 1;
184}
185
186void set_script_reduction(int n)
187{
188  script_size_reduction = n;
189}
190
191const char *get_gfont()
192{
193  return gfont ? gfont : "I";
194}
195
196const char *get_grfont()
197{
198  return grfont ? grfont : "R";
199}
200
201const char *get_gbfont()
202{
203  return gbfont ? gbfont : "B";
204}
205
206void set_gfont(const char *s)
207{
208  a_delete gfont;
209  gfont = strsave(s);
210}
211
212void set_grfont(const char *s)
213{
214  a_delete grfont;
215  grfont = strsave(s);
216}
217
218void set_gbfont(const char *s)
219{
220  a_delete gbfont;
221  gbfont = strsave(s);
222}
223
224// this must be precisely 2 characters in length
225#define COMPATIBLE_REG "0C"
226
227void start_string()
228{
229  printf(".nr " COMPATIBLE_REG " \\n(.C\n");
230  printf(".cp 0\n");
231  printf(".ds " LINE_STRING "\n");
232}
233
234void output_string()
235{
236  printf("\\*(" LINE_STRING "\n");
237}
238
239void restore_compatibility()
240{
241  printf(".cp \\n(" COMPATIBLE_REG "\n");
242}
243
244void do_text(const char *s)
245{
246  printf(".eo\n");
247  printf(".as " LINE_STRING " \"%s\n", s);
248  printf(".ec\n");
249}
250
251void set_minimum_size(int n)
252{
253  minimum_size = n;
254}
255
256void set_script_size()
257{
258  if (minimum_size < 0)
259    minimum_size = 0;
260  if (script_size_reduction >= 0)
261    printf(".ps \\n[.s]-%d>?%d\n", script_size_reduction, minimum_size);
262  else
263    printf(".ps (u;\\n[.ps]*7+5/10>?%d)\n", minimum_size);
264}
265
266int box::next_uid = 0;
267
268box::box() : spacing_type(ORDINARY_TYPE), uid(next_uid++)
269{
270}
271
272box::~box()
273{
274}
275
276void box::top_level()
277{
278  // debug_print();
279  // putc('\n', stderr);
280  box *b = this;
281  printf(".nr " SAVED_FONT_REG " \\n[.f]\n");
282  printf(".ft\n");
283  printf(".nr " SAVED_PREV_FONT_REG " \\n[.f]\n");
284  printf(".ft %s\n", get_gfont());
285  printf(".nr " SAVED_SIZE_REG " \\n[.ps]\n");
286  if (gsize > 0) {
287    char buf[INT_DIGITS + 1];
288    sprintf(buf, "%d", gsize);
289    b = new size_box(strsave(buf), b);
290  }
291  current_roman_font = get_grfont();
292  // This catches tabs used within \Z (which aren't allowed).
293  b->check_tabs(0);
294  int r = b->compute_metrics(DISPLAY_STYLE);
295  printf(".ft \\n[" SAVED_PREV_FONT_REG "]\n");
296  printf(".ft \\n[" SAVED_FONT_REG "]\n");
297  printf(".nr " MARK_OR_LINEUP_FLAG_REG " %d\n", r);
298  if (r == FOUND_MARK) {
299    printf(".nr " SAVED_MARK_REG " \\n[" MARK_REG "]\n");
300    printf(".nr " MARK_WIDTH_REG " 0\\n[" WIDTH_FORMAT "]\n", b->uid);
301  }
302  else if (r == FOUND_LINEUP)
303    printf(".if r" SAVED_MARK_REG " .as1 " LINE_STRING " \\h'\\n["
304	   SAVED_MARK_REG "]u-\\n[" MARK_REG "]u'\n");
305  else
306    assert(r == FOUND_NOTHING);
307  // The problem here is that the argument to \f is read in copy mode,
308  // so we cannot use \E there; so we hide it in a string instead.
309  // Another problem is that if we use \R directly, then the space will
310  // prevent it working in a macro argument.
311  printf(".ds " SAVE_FONT_STRING " "
312	 "\\R'" SAVED_INLINE_FONT_REG " \\\\n[.f]'"
313	 "\\fP"
314	 "\\R'" SAVED_INLINE_PREV_FONT_REG " \\\\n[.f]'"
315	 "\\R'" SAVED_INLINE_SIZE_REG " \\\\n[.ps]'"
316	 "\\s0"
317	 "\\R'" SAVED_INLINE_PREV_SIZE_REG " \\\\n[.ps]'"
318	 "\n"
319	 ".ds " RESTORE_FONT_STRING " "
320	 "\\f[\\\\n[" SAVED_INLINE_PREV_FONT_REG "]]"
321	 "\\f[\\\\n[" SAVED_INLINE_FONT_REG "]]"
322	 "\\s'\\\\n[" SAVED_INLINE_PREV_SIZE_REG "]u'"
323	 "\\s'\\\\n[" SAVED_INLINE_SIZE_REG "]u'"
324	 "\n");
325  printf(".as1 " LINE_STRING " \\&\\E*[" SAVE_FONT_STRING "]");
326  printf("\\f[%s]", get_gfont());
327  printf("\\s'\\En[" SAVED_SIZE_REG "]u'");
328  current_roman_font = get_grfont();
329  b->output();
330  printf("\\E*[" RESTORE_FONT_STRING "]\n");
331  if (r == FOUND_LINEUP)
332    printf(".if r" SAVED_MARK_REG " .as1 " LINE_STRING " \\h'\\n["
333	   MARK_WIDTH_REG "]u-\\n[" SAVED_MARK_REG "]u-(\\n["
334	   WIDTH_FORMAT "]u-\\n[" MARK_REG "]u)'\n",
335	   b->uid);
336  b->extra_space();
337  if (!inline_flag)
338    printf(".ne \\n[" HEIGHT_FORMAT "]u-%dM>?0+(\\n["
339	   DEPTH_FORMAT "]u-%dM>?0)\n",
340	   b->uid, body_height, b->uid, body_depth);
341  delete b;
342  next_uid = 0;
343}
344
345// gpic defines this register so as to make geqn not produce `\x's
346#define EQN_NO_EXTRA_SPACE_REG "0x"
347
348void box::extra_space()
349{
350  printf(".if !r" EQN_NO_EXTRA_SPACE_REG " "
351	 ".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
352  if (positive_space >= 0 || negative_space >= 0) {
353    if (positive_space > 0)
354      printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
355	     ".as1 " LINE_STRING " \\x'-%dM'\n", positive_space);
356    if (negative_space > 0)
357      printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
358	     ".as1 " LINE_STRING " \\x'%dM'\n", negative_space);
359    positive_space = negative_space = -1;
360  }
361  else {
362    printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
363	   ".if \\n[" HEIGHT_FORMAT "]>%dM .as1 " LINE_STRING
364	   " \\x'-(\\n[" HEIGHT_FORMAT
365	   "]u-%dM)'\n",
366	   uid, body_height, uid, body_height);
367    printf(".if !\\n[" EQN_NO_EXTRA_SPACE_REG "] "
368	   ".if \\n[" DEPTH_FORMAT "]>%dM .as1 " LINE_STRING
369	   " \\x'\\n[" DEPTH_FORMAT
370	   "]u-%dM'\n",
371	   uid, body_depth, uid, body_depth);
372  }
373}
374
375int box::compute_metrics(int)
376{
377  printf(".nr " WIDTH_FORMAT " 0\n", uid);
378  printf(".nr " HEIGHT_FORMAT " 0\n", uid);
379  printf(".nr " DEPTH_FORMAT " 0\n", uid);
380  return FOUND_NOTHING;
381}
382
383void box::compute_subscript_kern()
384{
385  printf(".nr " SUB_KERN_FORMAT " 0\n", uid);
386}
387
388void box::compute_skew()
389{
390  printf(".nr " SKEW_FORMAT " 0\n", uid);
391}
392
393void box::output()
394{
395}
396
397void box::check_tabs(int)
398{
399}
400
401int box::is_char()
402{
403  return 0;
404}
405
406int box::left_is_italic()
407{
408  return 0;
409}
410
411int box::right_is_italic()
412{
413  return 0;
414}
415
416void box::hint(unsigned)
417{
418}
419
420void box::handle_char_type(int, int)
421{
422}
423
424
425box_list::box_list(box *pp)
426{
427  p = new box*[10];
428  for (int i = 0; i < 10; i++)
429    p[i] = 0;
430  maxlen = 10;
431  len = 1;
432  p[0] = pp;
433}
434
435void box_list::append(box *pp)
436{
437  if (len + 1 > maxlen) {
438    box **oldp = p;
439    maxlen *= 2;
440    p = new box*[maxlen];
441    memcpy(p, oldp, sizeof(box*)*len);
442    a_delete oldp;
443  }
444  p[len++] = pp;
445}
446
447box_list::~box_list()
448{
449  for (int i = 0; i < len; i++)
450    delete p[i];
451  a_delete p;
452}
453
454void box_list::list_check_tabs(int level)
455{
456  for (int i = 0; i < len; i++)
457    p[i]->check_tabs(level);
458}
459
460
461pointer_box::pointer_box(box *pp) : p(pp)
462{
463  spacing_type = p->spacing_type;
464}
465
466pointer_box::~pointer_box()
467{
468  delete p;
469}
470
471int pointer_box::compute_metrics(int style)
472{
473  int r = p->compute_metrics(style);
474  printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]\n", uid, p->uid);
475  printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]\n", uid, p->uid);
476  printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]\n", uid, p->uid);
477  return r;
478}
479
480void pointer_box::compute_subscript_kern()
481{
482  p->compute_subscript_kern();
483  printf(".nr " SUB_KERN_FORMAT " \\n[" SUB_KERN_FORMAT "]\n", uid, p->uid);
484}
485
486void pointer_box::compute_skew()
487{
488  p->compute_skew();
489  printf(".nr " SKEW_FORMAT " 0\\n[" SKEW_FORMAT "]\n",
490	 uid, p->uid);
491}
492
493void pointer_box::check_tabs(int level)
494{
495  p->check_tabs(level);
496}
497
498int simple_box::compute_metrics(int)
499{
500  printf(".nr " WIDTH_FORMAT " 0\\w" DELIMITER_CHAR, uid);
501  output();
502  printf(DELIMITER_CHAR "\n");
503  printf(".nr " HEIGHT_FORMAT " 0>?\\n[rst]\n", uid);
504  printf(".nr " DEPTH_FORMAT " 0-\\n[rsb]>?0\n", uid);
505  printf(".nr " SUB_KERN_FORMAT " 0-\\n[ssc]>?0\n", uid);
506  printf(".nr " SKEW_FORMAT " 0\\n[skw]\n", uid);
507  return FOUND_NOTHING;
508}
509
510void simple_box::compute_subscript_kern()
511{
512  // do nothing, we already computed it in do_metrics
513}
514
515void simple_box::compute_skew()
516{
517  // do nothing, we already computed it in do_metrics
518}
519
520int box::is_simple()
521{
522  return 0;
523}
524
525int simple_box::is_simple()
526{
527  return 1;
528}
529
530quoted_text_box::quoted_text_box(char *s) : text(s)
531{
532}
533
534quoted_text_box::~quoted_text_box()
535{
536  a_delete text;
537}
538
539void quoted_text_box::output()
540{
541  if (text)
542    fputs(text, stdout);
543}
544
545tab_box::tab_box() : disabled(0)
546{
547}
548
549// We treat a tab_box as having width 0 for width computations.
550
551void tab_box::output()
552{
553  if (!disabled)
554    printf("\\t");
555}
556
557void tab_box::check_tabs(int level)
558{
559  if (level > 0) {
560    error("tabs allowed only at outermost level");
561    disabled = 1;
562  }
563}
564
565space_box::space_box()
566{
567  spacing_type = SUPPRESS_TYPE;
568}
569
570void space_box::output()
571{
572  printf("\\h'%dM'", thick_space);
573}
574
575half_space_box::half_space_box()
576{
577  spacing_type = SUPPRESS_TYPE;
578}
579
580void half_space_box::output()
581{
582  printf("\\h'%dM'", thin_space);
583}
584
585void box_list::list_debug_print(const char *sep)
586{
587  p[0]->debug_print();
588  for (int i = 1; i < len; i++) {
589    fprintf(stderr, "%s", sep);
590    p[i]->debug_print();
591  }
592}
593
594void quoted_text_box::debug_print()
595{
596  fprintf(stderr, "\"%s\"", (text ? text : ""));
597}
598
599void half_space_box::debug_print()
600{
601  fprintf(stderr, "^");
602}
603
604void space_box::debug_print()
605{
606  fprintf(stderr, "~");
607}
608
609void tab_box::debug_print()
610{
611  fprintf(stderr, "<tab>");
612}
613