Merge branch 'vendor/MDOCML'
[dragonfly.git] / contrib / mdocml / tbl_layout.c
1 /*      $Id: tbl_layout.c,v 1.48 2018/12/14 05:18:03 schwarze Exp $ */
2 /*
3  * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2012, 2014, 2015, 2017 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 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <ctype.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "tbl.h"
32 #include "libmandoc.h"
33 #include "tbl_int.h"
34
35 struct  tbl_phrase {
36         char             name;
37         enum tbl_cellt   key;
38 };
39
40 static  const struct tbl_phrase keys[] = {
41         { 'c',           TBL_CELL_CENTRE },
42         { 'r',           TBL_CELL_RIGHT },
43         { 'l',           TBL_CELL_LEFT },
44         { 'n',           TBL_CELL_NUMBER },
45         { 's',           TBL_CELL_SPAN },
46         { 'a',           TBL_CELL_LONG },
47         { '^',           TBL_CELL_DOWN },
48         { '-',           TBL_CELL_HORIZ },
49         { '_',           TBL_CELL_HORIZ },
50         { '=',           TBL_CELL_DHORIZ }
51 };
52
53 #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
54
55 static  void             mods(struct tbl_node *, struct tbl_cell *,
56                                 int, const char *, int *);
57 static  void             cell(struct tbl_node *, struct tbl_row *,
58                                 int, const char *, int *);
59 static  struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
60                                 enum tbl_cellt);
61
62
63 static void
64 mods(struct tbl_node *tbl, struct tbl_cell *cp,
65                 int ln, const char *p, int *pos)
66 {
67         char            *endptr;
68         size_t           sz;
69
70 mod:
71         while (p[*pos] == ' ' || p[*pos] == '\t')
72                 (*pos)++;
73
74         /* Row delimiters and cell specifiers end modifier lists. */
75
76         if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
77                 return;
78
79         /* Throw away parenthesised expression. */
80
81         if ('(' == p[*pos]) {
82                 (*pos)++;
83                 while (p[*pos] && ')' != p[*pos])
84                         (*pos)++;
85                 if (')' == p[*pos]) {
86                         (*pos)++;
87                         goto mod;
88                 }
89                 mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
90                 return;
91         }
92
93         /* Parse numerical spacing from modifier string. */
94
95         if (isdigit((unsigned char)p[*pos])) {
96                 cp->spacing = strtoull(p + *pos, &endptr, 10);
97                 *pos = endptr - p;
98                 goto mod;
99         }
100
101         switch (tolower((unsigned char)p[(*pos)++])) {
102         case 'b':
103                 cp->flags |= TBL_CELL_BOLD;
104                 goto mod;
105         case 'd':
106                 cp->flags |= TBL_CELL_BALIGN;
107                 goto mod;
108         case 'e':
109                 cp->flags |= TBL_CELL_EQUAL;
110                 goto mod;
111         case 'f':
112                 break;
113         case 'i':
114                 cp->flags |= TBL_CELL_ITALIC;
115                 goto mod;
116         case 'm':
117                 mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
118                 goto mod;
119         case 'p':
120         case 'v':
121                 if (p[*pos] == '-' || p[*pos] == '+')
122                         (*pos)++;
123                 while (isdigit((unsigned char)p[*pos]))
124                         (*pos)++;
125                 goto mod;
126         case 't':
127                 cp->flags |= TBL_CELL_TALIGN;
128                 goto mod;
129         case 'u':
130                 cp->flags |= TBL_CELL_UP;
131                 goto mod;
132         case 'w':
133                 sz = 0;
134                 if (p[*pos] == '(') {
135                         (*pos)++;
136                         while (p[*pos + sz] != '\0' && p[*pos + sz] != ')')
137                                 sz++;
138                 } else
139                         while (isdigit((unsigned char)p[*pos + sz]))
140                                 sz++;
141                 if (sz) {
142                         free(cp->wstr);
143                         cp->wstr = mandoc_strndup(p + *pos, sz);
144                         *pos += sz;
145                         if (p[*pos] == ')')
146                                 (*pos)++;
147                 }
148                 goto mod;
149         case 'x':
150                 cp->flags |= TBL_CELL_WMAX;
151                 goto mod;
152         case 'z':
153                 cp->flags |= TBL_CELL_WIGN;
154                 goto mod;
155         case '|':
156                 if (cp->vert < 2)
157                         cp->vert++;
158                 else
159                         mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
160                             ln, *pos - 1, NULL);
161                 goto mod;
162         default:
163                 mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
164                     ln, *pos - 1, "%c", p[*pos - 1]);
165                 goto mod;
166         }
167
168         /* Ignore parenthised font names for now. */
169
170         if (p[*pos] == '(')
171                 goto mod;
172
173         /* Support only one-character font-names for now. */
174
175         if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
176                 mandoc_msg(MANDOCERR_FT_BAD,
177                     ln, *pos, "TS %s", p + *pos - 1);
178                 if (p[*pos] != '\0')
179                         (*pos)++;
180                 if (p[*pos] != '\0')
181                         (*pos)++;
182                 goto mod;
183         }
184
185         switch (p[(*pos)++]) {
186         case '3':
187         case 'B':
188                 cp->flags |= TBL_CELL_BOLD;
189                 goto mod;
190         case '2':
191         case 'I':
192                 cp->flags |= TBL_CELL_ITALIC;
193                 goto mod;
194         case '1':
195         case 'R':
196                 goto mod;
197         default:
198                 mandoc_msg(MANDOCERR_FT_BAD,
199                     ln, *pos - 1, "TS f%c", p[*pos - 1]);
200                 goto mod;
201         }
202 }
203
204 static void
205 cell(struct tbl_node *tbl, struct tbl_row *rp,
206                 int ln, const char *p, int *pos)
207 {
208         int              i;
209         enum tbl_cellt   c;
210
211         /* Handle leading vertical lines */
212
213         while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
214                 if (p[*pos] == '|') {
215                         if (rp->vert < 2)
216                                 rp->vert++;
217                         else
218                                 mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
219                                     ln, *pos, NULL);
220                 }
221                 (*pos)++;
222         }
223
224 again:
225         while (p[*pos] == ' ' || p[*pos] == '\t')
226                 (*pos)++;
227
228         if (p[*pos] == '.' || p[*pos] == '\0')
229                 return;
230
231         /* Parse the column position (`c', `l', `r', ...). */
232
233         for (i = 0; i < KEYS_MAX; i++)
234                 if (tolower((unsigned char)p[*pos]) == keys[i].name)
235                         break;
236
237         if (i == KEYS_MAX) {
238                 mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
239                     ln, *pos, "%c", p[*pos]);
240                 (*pos)++;
241                 goto again;
242         }
243         c = keys[i].key;
244
245         /* Special cases of spanners. */
246
247         if (c == TBL_CELL_SPAN) {
248                 if (rp->last == NULL)
249                         mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
250                 else if (rp->last->pos == TBL_CELL_HORIZ ||
251                     rp->last->pos == TBL_CELL_DHORIZ)
252                         c = rp->last->pos;
253         } else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
254                 mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);
255
256         (*pos)++;
257
258         /* Allocate cell then parse its modifiers. */
259
260         mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
261 }
262
263 void
264 tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
265 {
266         struct tbl_row  *rp;
267
268         rp = NULL;
269         for (;;) {
270                 /* Skip whitespace before and after each cell. */
271
272                 while (p[pos] == ' ' || p[pos] == '\t')
273                         pos++;
274
275                 switch (p[pos]) {
276                 case ',':  /* Next row on this input line. */
277                         pos++;
278                         rp = NULL;
279                         continue;
280                 case '\0':  /* Next row on next input line. */
281                         return;
282                 case '.':  /* End of layout. */
283                         pos++;
284                         tbl->part = TBL_PART_DATA;
285
286                         /*
287                          * When the layout is completely empty,
288                          * default to one left-justified column.
289                          */
290
291                         if (tbl->first_row == NULL) {
292                                 tbl->first_row = tbl->last_row =
293                                     mandoc_calloc(1, sizeof(*rp));
294                         }
295                         if (tbl->first_row->first == NULL) {
296                                 mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
297                                     ln, pos, NULL);
298                                 cell_alloc(tbl, tbl->first_row,
299                                     TBL_CELL_LEFT);
300                                 if (tbl->opts.lvert < tbl->first_row->vert)
301                                         tbl->opts.lvert = tbl->first_row->vert;
302                                 return;
303                         }
304
305                         /*
306                          * Search for the widest line
307                          * along the left and right margins.
308                          */
309
310                         for (rp = tbl->first_row; rp; rp = rp->next) {
311                                 if (tbl->opts.lvert < rp->vert)
312                                         tbl->opts.lvert = rp->vert;
313                                 if (rp->last != NULL &&
314                                     rp->last->col + 1 == tbl->opts.cols &&
315                                     tbl->opts.rvert < rp->last->vert)
316                                         tbl->opts.rvert = rp->last->vert;
317
318                                 /* If the last line is empty, drop it. */
319
320                                 if (rp->next != NULL &&
321                                     rp->next->first == NULL) {
322                                         free(rp->next);
323                                         rp->next = NULL;
324                                         tbl->last_row = rp;
325                                 }
326                         }
327                         return;
328                 default:  /* Cell. */
329                         break;
330                 }
331
332                 /*
333                  * If the last line had at least one cell,
334                  * start a new one; otherwise, continue it.
335                  */
336
337                 if (rp == NULL) {
338                         if (tbl->last_row == NULL ||
339                             tbl->last_row->first != NULL) {
340                                 rp = mandoc_calloc(1, sizeof(*rp));
341                                 if (tbl->last_row)
342                                         tbl->last_row->next = rp;
343                                 else
344                                         tbl->first_row = rp;
345                                 tbl->last_row = rp;
346                         } else
347                                 rp = tbl->last_row;
348                 }
349                 cell(tbl, rp, ln, p, &pos);
350         }
351 }
352
353 static struct tbl_cell *
354 cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
355 {
356         struct tbl_cell *p, *pp;
357
358         p = mandoc_calloc(1, sizeof(*p));
359         p->spacing = SIZE_MAX;
360         p->pos = pos;
361
362         if ((pp = rp->last) != NULL) {
363                 pp->next = p;
364                 p->col = pp->col + 1;
365         } else
366                 rp->first = p;
367         rp->last = p;
368
369         if (tbl->opts.cols <= p->col)
370                 tbl->opts.cols = p->col + 1;
371
372         return p;
373 }