Merge branch 'vendor/MDOCML'
[dragonfly.git] / contrib / mdocml / tbl_term.c
1 /*      $Id: tbl_term.c,v 1.21 2011/09/20 23:05:49 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <assert.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "mandoc.h"
28 #include "out.h"
29 #include "term.h"
30
31 static  size_t  term_tbl_len(size_t, void *);
32 static  size_t  term_tbl_strlen(const char *, void *);
33 static  void    tbl_char(struct termp *, char, size_t);
34 static  void    tbl_data(struct termp *, const struct tbl *,
35                         const struct tbl_dat *, 
36                         const struct roffcol *);
37 static  size_t  tbl_rulewidth(struct termp *, const struct tbl_head *);
38 static  void    tbl_hframe(struct termp *, const struct tbl_span *, int);
39 static  void    tbl_literal(struct termp *, const struct tbl_dat *, 
40                         const struct roffcol *);
41 static  void    tbl_number(struct termp *, const struct tbl *, 
42                         const struct tbl_dat *, 
43                         const struct roffcol *);
44 static  void    tbl_hrule(struct termp *, const struct tbl_span *);
45 static  void    tbl_vrule(struct termp *, const struct tbl_head *);
46
47
48 static size_t
49 term_tbl_strlen(const char *p, void *arg)
50 {
51
52         return(term_strlen((const struct termp *)arg, p));
53 }
54
55 static size_t
56 term_tbl_len(size_t sz, void *arg)
57 {
58
59         return(term_len((const struct termp *)arg, sz));
60 }
61
62 void
63 term_tbl(struct termp *tp, const struct tbl_span *sp)
64 {
65         const struct tbl_head   *hp;
66         const struct tbl_dat    *dp;
67         struct roffcol          *col;
68         int                      spans;
69         size_t                   rmargin, maxrmargin;
70
71         rmargin = tp->rmargin;
72         maxrmargin = tp->maxrmargin;
73
74         tp->rmargin = tp->maxrmargin = TERM_MAXMARGIN;
75
76         /* Inhibit printing of spaces: we do padding ourselves. */
77
78         tp->flags |= TERMP_NONOSPACE;
79         tp->flags |= TERMP_NOSPACE;
80
81         /*
82          * The first time we're invoked for a given table block,
83          * calculate the table widths and decimal positions.
84          */
85
86         if (TBL_SPAN_FIRST & sp->flags) {
87                 term_flushln(tp);
88
89                 tp->tbl.len = term_tbl_len;
90                 tp->tbl.slen = term_tbl_strlen;
91                 tp->tbl.arg = tp;
92
93                 tblcalc(&tp->tbl, sp);
94         }
95
96         /* Horizontal frame at the start of boxed tables. */
97
98         if (TBL_SPAN_FIRST & sp->flags) {
99                 if (TBL_OPT_DBOX & sp->tbl->opts)
100                         tbl_hframe(tp, sp, 1);
101                 if (TBL_OPT_DBOX & sp->tbl->opts ||
102                     TBL_OPT_BOX  & sp->tbl->opts)
103                         tbl_hframe(tp, sp, 0);
104         }
105
106         /* Vertical frame at the start of each row. */
107
108         if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
109                 term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
110                         TBL_SPAN_DHORIZ == sp->pos ? "+" : "|");
111
112         /*
113          * Now print the actual data itself depending on the span type.
114          * Spanner spans get a horizontal rule; data spanners have their
115          * data printed by matching data to header.
116          */
117
118         switch (sp->pos) {
119         case (TBL_SPAN_HORIZ):
120                 /* FALLTHROUGH */
121         case (TBL_SPAN_DHORIZ):
122                 tbl_hrule(tp, sp);
123                 break;
124         case (TBL_SPAN_DATA):
125                 /* Iterate over template headers. */
126                 dp = sp->first;
127                 spans = 0;
128                 for (hp = sp->head; hp; hp = hp->next) {
129                         /* 
130                          * If the current data header is invoked during
131                          * a spanner ("spans" > 0), don't emit anything
132                          * at all.
133                          */
134                         switch (hp->pos) {
135                         case (TBL_HEAD_VERT):
136                                 /* FALLTHROUGH */
137                         case (TBL_HEAD_DVERT):
138                                 if (spans <= 0)
139                                         tbl_vrule(tp, hp);
140                                 continue;
141                         case (TBL_HEAD_DATA):
142                                 break;
143                         }
144
145                         if (--spans >= 0)
146                                 continue;
147
148                         /*
149                          * All cells get a leading blank, except the
150                          * first one and those after double rulers.
151                          */
152
153                         if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
154                                 tbl_char(tp, ASCII_NBRSP, 1);
155
156                         col = &tp->tbl.cols[hp->ident];
157                         tbl_data(tp, sp->tbl, dp, col);
158
159                         /* No trailing blanks. */
160
161                         if (NULL == hp->next)
162                                 break;
163
164                         /*
165                          * Add another blank between cells,
166                          * or two when there is no vertical ruler.
167                          */
168
169                         tbl_char(tp, ASCII_NBRSP,
170                             TBL_HEAD_VERT  == hp->next->pos ||
171                             TBL_HEAD_DVERT == hp->next->pos ? 1 : 2);
172
173                         /* 
174                          * Go to the next data cell and assign the
175                          * number of subsequent spans, if applicable.
176                          */
177
178                         if (dp) {
179                                 spans = dp->spans;
180                                 dp = dp->next;
181                         }
182                 }
183                 break;
184         }
185
186         /* Vertical frame at the end of each row. */
187
188         if (TBL_OPT_BOX & sp->tbl->opts || TBL_OPT_DBOX & sp->tbl->opts)
189                 term_word(tp, TBL_SPAN_HORIZ == sp->pos ||
190                         TBL_SPAN_DHORIZ == sp->pos ? "+" : " |");
191         term_flushln(tp);
192
193         /*
194          * If we're the last row, clean up after ourselves: clear the
195          * existing table configuration and set it to NULL.
196          */
197
198         if (TBL_SPAN_LAST & sp->flags) {
199                 if (TBL_OPT_DBOX & sp->tbl->opts ||
200                     TBL_OPT_BOX  & sp->tbl->opts)
201                         tbl_hframe(tp, sp, 0);
202                 if (TBL_OPT_DBOX & sp->tbl->opts)
203                         tbl_hframe(tp, sp, 1);
204                 assert(tp->tbl.cols);
205                 free(tp->tbl.cols);
206                 tp->tbl.cols = NULL;
207         }
208
209         tp->flags &= ~TERMP_NONOSPACE;
210         tp->rmargin = rmargin;
211         tp->maxrmargin = maxrmargin;
212
213 }
214
215 /*
216  * Horizontal rules extend across the entire table.
217  * Calculate the width by iterating over columns.
218  */
219 static size_t
220 tbl_rulewidth(struct termp *tp, const struct tbl_head *hp)
221 {
222         size_t           width;
223
224         width = tp->tbl.cols[hp->ident].width;
225         if (TBL_HEAD_DATA == hp->pos) {
226                 /* Account for leading blanks. */
227                 if (hp->prev && TBL_HEAD_DVERT != hp->prev->pos)
228                         width++;
229                 /* Account for trailing blanks. */
230                 width++;
231                 if (hp->next &&
232                     TBL_HEAD_VERT  != hp->next->pos &&
233                     TBL_HEAD_DVERT != hp->next->pos)
234                         width++;
235         }
236         return(width);
237 }
238
239 /*
240  * Rules inside the table can be single or double
241  * and have crossings with vertical rules marked with pluses.
242  */
243 static void
244 tbl_hrule(struct termp *tp, const struct tbl_span *sp)
245 {
246         const struct tbl_head *hp;
247         char             c;
248
249         c = '-';
250         if (TBL_SPAN_DHORIZ == sp->pos)
251                 c = '=';
252
253         for (hp = sp->head; hp; hp = hp->next)
254                 tbl_char(tp,
255                     TBL_HEAD_DATA == hp->pos ? c : '+',
256                     tbl_rulewidth(tp, hp));
257 }
258
259 /*
260  * Rules above and below the table are always single
261  * and have an additional plus at the beginning and end.
262  * For double frames, this function is called twice,
263  * and the outer one does not have crossings.
264  */
265 static void
266 tbl_hframe(struct termp *tp, const struct tbl_span *sp, int outer)
267 {
268         const struct tbl_head *hp;
269
270         term_word(tp, "+");
271         for (hp = sp->head; hp; hp = hp->next)
272                 tbl_char(tp,
273                     outer || TBL_HEAD_DATA == hp->pos ? '-' : '+',
274                     tbl_rulewidth(tp, hp));
275         term_word(tp, "+");
276         term_flushln(tp);
277 }
278
279 static void
280 tbl_data(struct termp *tp, const struct tbl *tbl,
281                 const struct tbl_dat *dp, 
282                 const struct roffcol *col)
283 {
284
285         if (NULL == dp) {
286                 tbl_char(tp, ASCII_NBRSP, col->width);
287                 return;
288         }
289         assert(dp->layout);
290
291         switch (dp->pos) {
292         case (TBL_DATA_NONE):
293                 tbl_char(tp, ASCII_NBRSP, col->width);
294                 return;
295         case (TBL_DATA_HORIZ):
296                 /* FALLTHROUGH */
297         case (TBL_DATA_NHORIZ):
298                 tbl_char(tp, '-', col->width);
299                 return;
300         case (TBL_DATA_NDHORIZ):
301                 /* FALLTHROUGH */
302         case (TBL_DATA_DHORIZ):
303                 tbl_char(tp, '=', col->width);
304                 return;
305         default:
306                 break;
307         }
308         
309         switch (dp->layout->pos) {
310         case (TBL_CELL_HORIZ):
311                 tbl_char(tp, '-', col->width);
312                 break;
313         case (TBL_CELL_DHORIZ):
314                 tbl_char(tp, '=', col->width);
315                 break;
316         case (TBL_CELL_LONG):
317                 /* FALLTHROUGH */
318         case (TBL_CELL_CENTRE):
319                 /* FALLTHROUGH */
320         case (TBL_CELL_LEFT):
321                 /* FALLTHROUGH */
322         case (TBL_CELL_RIGHT):
323                 tbl_literal(tp, dp, col);
324                 break;
325         case (TBL_CELL_NUMBER):
326                 tbl_number(tp, tbl, dp, col);
327                 break;
328         case (TBL_CELL_DOWN):
329                 tbl_char(tp, ASCII_NBRSP, col->width);
330                 break;
331         default:
332                 abort();
333                 /* NOTREACHED */
334         }
335 }
336
337 static void
338 tbl_vrule(struct termp *tp, const struct tbl_head *hp)
339 {
340
341         switch (hp->pos) {
342         case (TBL_HEAD_VERT):
343                 term_word(tp, "|");
344                 break;
345         case (TBL_HEAD_DVERT):
346                 term_word(tp, "||");
347                 break;
348         default:
349                 break;
350         }
351 }
352
353 static void
354 tbl_char(struct termp *tp, char c, size_t len)
355 {
356         size_t          i, sz;
357         char            cp[2];
358
359         cp[0] = c;
360         cp[1] = '\0';
361
362         sz = term_strlen(tp, cp);
363
364         for (i = 0; i < len; i += sz)
365                 term_word(tp, cp);
366 }
367
368 static void
369 tbl_literal(struct termp *tp, const struct tbl_dat *dp, 
370                 const struct roffcol *col)
371 {
372         size_t           len, padl, padr;
373
374         assert(dp->string);
375         len = term_strlen(tp, dp->string);
376         padr = col->width > len ? col->width - len : 0;
377         padl = 0;
378
379         switch (dp->layout->pos) {
380         case (TBL_CELL_LONG):
381                 padl = term_len(tp, 1);
382                 padr = padr > padl ? padr - padl : 0;
383                 break;
384         case (TBL_CELL_CENTRE):
385                 if (2 > padr)
386                         break;
387                 padl = padr / 2;
388                 padr -= padl;
389                 break;
390         case (TBL_CELL_RIGHT):
391                 padl = padr;
392                 padr = 0;
393                 break;
394         default:
395                 break;
396         }
397
398         tbl_char(tp, ASCII_NBRSP, padl);
399         term_word(tp, dp->string);
400         tbl_char(tp, ASCII_NBRSP, padr);
401 }
402
403 static void
404 tbl_number(struct termp *tp, const struct tbl *tbl,
405                 const struct tbl_dat *dp,
406                 const struct roffcol *col)
407 {
408         char            *cp;
409         char             buf[2];
410         size_t           sz, psz, ssz, d, padl;
411         int              i;
412
413         /*
414          * See calc_data_number().  Left-pad by taking the offset of our
415          * and the maximum decimal; right-pad by the remaining amount.
416          */
417
418         assert(dp->string);
419
420         sz = term_strlen(tp, dp->string);
421
422         buf[0] = tbl->decimal;
423         buf[1] = '\0';
424
425         psz = term_strlen(tp, buf);
426
427         if (NULL != (cp = strrchr(dp->string, tbl->decimal))) {
428                 buf[1] = '\0';
429                 for (ssz = 0, i = 0; cp != &dp->string[i]; i++) {
430                         buf[0] = dp->string[i];
431                         ssz += term_strlen(tp, buf);
432                 }
433                 d = ssz + psz;
434         } else
435                 d = sz + psz;
436
437         padl = col->decimal - d;
438
439         tbl_char(tp, ASCII_NBRSP, padl);
440         term_word(tp, dp->string);
441         if (col->width > sz + padl)
442                 tbl_char(tp, ASCII_NBRSP, col->width - sz - padl);
443 }
444