1*6167eca2Schristos /* Id: tbl_layout.c,v 1.48 2018/12/14 05:18:03 schwarze Exp */
2c0d9444aSjoerg /*
3b1e8115bSjoerg * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
414e7489eSchristos * Copyright (c) 2012, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
5c0d9444aSjoerg *
6c0d9444aSjoerg * Permission to use, copy, modify, and distribute this software for any
7c0d9444aSjoerg * purpose with or without fee is hereby granted, provided that the above
8c0d9444aSjoerg * copyright notice and this permission notice appear in all copies.
9c0d9444aSjoerg *
10c0d9444aSjoerg * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11c0d9444aSjoerg * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12c0d9444aSjoerg * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13c0d9444aSjoerg * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14c0d9444aSjoerg * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15c0d9444aSjoerg * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16c0d9444aSjoerg * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17c0d9444aSjoerg */
18c09c2a7bSnakayama #include "config.h"
19c09c2a7bSnakayama
205c413d0cSchristos #include <sys/types.h>
215c413d0cSchristos
22c0d9444aSjoerg #include <ctype.h>
2314e7489eSchristos #include <stdint.h>
24*6167eca2Schristos #include <stdio.h>
25c0d9444aSjoerg #include <stdlib.h>
26c0d9444aSjoerg #include <string.h>
27c0d9444aSjoerg #include <time.h>
28c0d9444aSjoerg
295c413d0cSchristos #include "mandoc_aux.h"
30*6167eca2Schristos #include "mandoc.h"
31*6167eca2Schristos #include "tbl.h"
32c0d9444aSjoerg #include "libmandoc.h"
33*6167eca2Schristos #include "tbl_int.h"
34c0d9444aSjoerg
35c0d9444aSjoerg struct tbl_phrase {
36c0d9444aSjoerg char name;
37c0d9444aSjoerg enum tbl_cellt key;
38c0d9444aSjoerg };
39c0d9444aSjoerg
405c413d0cSchristos static const struct tbl_phrase keys[] = {
41c0d9444aSjoerg { 'c', TBL_CELL_CENTRE },
42c0d9444aSjoerg { 'r', TBL_CELL_RIGHT },
43c0d9444aSjoerg { 'l', TBL_CELL_LEFT },
44c0d9444aSjoerg { 'n', TBL_CELL_NUMBER },
45c0d9444aSjoerg { 's', TBL_CELL_SPAN },
46c0d9444aSjoerg { 'a', TBL_CELL_LONG },
47c0d9444aSjoerg { '^', TBL_CELL_DOWN },
48c0d9444aSjoerg { '-', TBL_CELL_HORIZ },
49c0d9444aSjoerg { '_', TBL_CELL_HORIZ },
50603fc4ebSjoerg { '=', TBL_CELL_DHORIZ }
51c0d9444aSjoerg };
52c0d9444aSjoerg
535c413d0cSchristos #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))
54c0d9444aSjoerg
555c413d0cSchristos static void mods(struct tbl_node *, struct tbl_cell *,
565c413d0cSchristos int, const char *, int *);
575c413d0cSchristos static void cell(struct tbl_node *, struct tbl_row *,
585c413d0cSchristos int, const char *, int *);
595c413d0cSchristos static struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
605c413d0cSchristos enum tbl_cellt);
615c413d0cSchristos
625c413d0cSchristos
635c413d0cSchristos static void
mods(struct tbl_node * tbl,struct tbl_cell * cp,int ln,const char * p,int * pos)64c0d9444aSjoerg mods(struct tbl_node *tbl, struct tbl_cell *cp,
65c0d9444aSjoerg int ln, const char *p, int *pos)
66c0d9444aSjoerg {
675c413d0cSchristos char *endptr;
6814e7489eSchristos size_t sz;
69b1e8115bSjoerg
70c0d9444aSjoerg mod:
715c413d0cSchristos while (p[*pos] == ' ' || p[*pos] == '\t')
725c413d0cSchristos (*pos)++;
735c413d0cSchristos
745c413d0cSchristos /* Row delimiters and cell specifiers end modifier lists. */
755c413d0cSchristos
765c413d0cSchristos if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
775c413d0cSchristos return;
78c0d9444aSjoerg
79c0d9444aSjoerg /* Throw away parenthesised expression. */
80c0d9444aSjoerg
81c0d9444aSjoerg if ('(' == p[*pos]) {
82c0d9444aSjoerg (*pos)++;
83c0d9444aSjoerg while (p[*pos] && ')' != p[*pos])
84c0d9444aSjoerg (*pos)++;
85c0d9444aSjoerg if (')' == p[*pos]) {
86c0d9444aSjoerg (*pos)++;
87c0d9444aSjoerg goto mod;
88c0d9444aSjoerg }
89*6167eca2Schristos mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
905c413d0cSchristos return;
91c0d9444aSjoerg }
92c0d9444aSjoerg
93c0d9444aSjoerg /* Parse numerical spacing from modifier string. */
94c0d9444aSjoerg
95c0d9444aSjoerg if (isdigit((unsigned char)p[*pos])) {
965c413d0cSchristos cp->spacing = strtoull(p + *pos, &endptr, 10);
975c413d0cSchristos *pos = endptr - p;
98c0d9444aSjoerg goto mod;
99c0d9444aSjoerg }
100c0d9444aSjoerg
101c0d9444aSjoerg switch (tolower((unsigned char)p[(*pos)++])) {
1025c413d0cSchristos case 'b':
103c0d9444aSjoerg cp->flags |= TBL_CELL_BOLD;
104c0d9444aSjoerg goto mod;
1055c413d0cSchristos case 'd':
1065c413d0cSchristos cp->flags |= TBL_CELL_BALIGN;
1075c413d0cSchristos goto mod;
1085c413d0cSchristos case 'e':
1095c413d0cSchristos cp->flags |= TBL_CELL_EQUAL;
1105c413d0cSchristos goto mod;
1115c413d0cSchristos case 'f':
1125c413d0cSchristos break;
1135c413d0cSchristos case 'i':
114c0d9444aSjoerg cp->flags |= TBL_CELL_ITALIC;
115c0d9444aSjoerg goto mod;
1165c413d0cSchristos case 'm':
117*6167eca2Schristos mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
1185c413d0cSchristos goto mod;
1195c413d0cSchristos case 'p':
1205c413d0cSchristos case 'v':
1215c413d0cSchristos if (p[*pos] == '-' || p[*pos] == '+')
1225c413d0cSchristos (*pos)++;
1235c413d0cSchristos while (isdigit((unsigned char)p[*pos]))
1245c413d0cSchristos (*pos)++;
1255c413d0cSchristos goto mod;
1265c413d0cSchristos case 't':
1275c413d0cSchristos cp->flags |= TBL_CELL_TALIGN;
1285c413d0cSchristos goto mod;
1295c413d0cSchristos case 'u':
1305c413d0cSchristos cp->flags |= TBL_CELL_UP;
1315c413d0cSchristos goto mod;
13214e7489eSchristos case 'w':
13314e7489eSchristos sz = 0;
13414e7489eSchristos if (p[*pos] == '(') {
13514e7489eSchristos (*pos)++;
13614e7489eSchristos while (p[*pos + sz] != '\0' && p[*pos + sz] != ')')
13714e7489eSchristos sz++;
13814e7489eSchristos } else
13914e7489eSchristos while (isdigit((unsigned char)p[*pos + sz]))
14014e7489eSchristos sz++;
14114e7489eSchristos if (sz) {
14214e7489eSchristos free(cp->wstr);
14314e7489eSchristos cp->wstr = mandoc_strndup(p + *pos, sz);
14414e7489eSchristos *pos += sz;
14514e7489eSchristos if (p[*pos] == ')')
14614e7489eSchristos (*pos)++;
14714e7489eSchristos }
1485c413d0cSchristos goto mod;
1495c413d0cSchristos case 'x':
1505c413d0cSchristos cp->flags |= TBL_CELL_WMAX;
1515c413d0cSchristos goto mod;
1525c413d0cSchristos case 'z':
1535c413d0cSchristos cp->flags |= TBL_CELL_WIGN;
1545c413d0cSchristos goto mod;
1555c413d0cSchristos case '|':
1565c413d0cSchristos if (cp->vert < 2)
1575c413d0cSchristos cp->vert++;
1585c413d0cSchristos else
1595c413d0cSchristos mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
160*6167eca2Schristos ln, *pos - 1, NULL);
161b1e8115bSjoerg goto mod;
162c0d9444aSjoerg default:
163*6167eca2Schristos mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
1645c413d0cSchristos ln, *pos - 1, "%c", p[*pos - 1]);
1655c413d0cSchristos goto mod;
166c0d9444aSjoerg }
167c0d9444aSjoerg
1685c413d0cSchristos /* Ignore parenthised font names for now. */
1695c413d0cSchristos
1705c413d0cSchristos if (p[*pos] == '(')
1715c413d0cSchristos goto mod;
1725c413d0cSchristos
1735c413d0cSchristos /* Support only one-character font-names for now. */
1745c413d0cSchristos
1755c413d0cSchristos if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) {
176*6167eca2Schristos mandoc_msg(MANDOCERR_FT_BAD,
1775c413d0cSchristos ln, *pos, "TS %s", p + *pos - 1);
1785c413d0cSchristos if (p[*pos] != '\0')
1795c413d0cSchristos (*pos)++;
1805c413d0cSchristos if (p[*pos] != '\0')
1815c413d0cSchristos (*pos)++;
1825c413d0cSchristos goto mod;
183c0d9444aSjoerg }
184c0d9444aSjoerg
1855c413d0cSchristos switch (p[(*pos)++]) {
1865c413d0cSchristos case '3':
1875c413d0cSchristos case 'B':
1885c413d0cSchristos cp->flags |= TBL_CELL_BOLD;
1895c413d0cSchristos goto mod;
1905c413d0cSchristos case '2':
1915c413d0cSchristos case 'I':
1925c413d0cSchristos cp->flags |= TBL_CELL_ITALIC;
1935c413d0cSchristos goto mod;
1945c413d0cSchristos case '1':
1955c413d0cSchristos case 'R':
1965c413d0cSchristos goto mod;
1975c413d0cSchristos default:
198*6167eca2Schristos mandoc_msg(MANDOCERR_FT_BAD,
1995c413d0cSchristos ln, *pos - 1, "TS f%c", p[*pos - 1]);
2005c413d0cSchristos goto mod;
2015c413d0cSchristos }
2025c413d0cSchristos }
2035c413d0cSchristos
2045c413d0cSchristos static void
cell(struct tbl_node * tbl,struct tbl_row * rp,int ln,const char * p,int * pos)205c0d9444aSjoerg cell(struct tbl_node *tbl, struct tbl_row *rp,
206c0d9444aSjoerg int ln, const char *p, int *pos)
207c0d9444aSjoerg {
2085c413d0cSchristos int i;
209c0d9444aSjoerg enum tbl_cellt c;
210c0d9444aSjoerg
2115c413d0cSchristos /* Handle leading vertical lines */
212603fc4ebSjoerg
2135c413d0cSchristos while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
2145c413d0cSchristos if (p[*pos] == '|') {
2155c413d0cSchristos if (rp->vert < 2)
2165c413d0cSchristos rp->vert++;
2175c413d0cSchristos else
2185c413d0cSchristos mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
219*6167eca2Schristos ln, *pos, NULL);
2205c413d0cSchristos }
221603fc4ebSjoerg (*pos)++;
2225c413d0cSchristos }
2235c413d0cSchristos
2245c413d0cSchristos again:
2255c413d0cSchristos while (p[*pos] == ' ' || p[*pos] == '\t')
2265c413d0cSchristos (*pos)++;
2275c413d0cSchristos
2285c413d0cSchristos if (p[*pos] == '.' || p[*pos] == '\0')
2295c413d0cSchristos return;
230603fc4ebSjoerg
231603fc4ebSjoerg /* Parse the column position (`c', `l', `r', ...). */
232c0d9444aSjoerg
233c0d9444aSjoerg for (i = 0; i < KEYS_MAX; i++)
234c0d9444aSjoerg if (tolower((unsigned char)p[*pos]) == keys[i].name)
235c0d9444aSjoerg break;
236c0d9444aSjoerg
2375c413d0cSchristos if (i == KEYS_MAX) {
238*6167eca2Schristos mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
2395c413d0cSchristos ln, *pos, "%c", p[*pos]);
2405c413d0cSchristos (*pos)++;
2415c413d0cSchristos goto again;
242c0d9444aSjoerg }
243c0d9444aSjoerg c = keys[i].key;
244c0d9444aSjoerg
2455c413d0cSchristos /* Special cases of spanners. */
246c0d9444aSjoerg
2475c413d0cSchristos if (c == TBL_CELL_SPAN) {
2485c413d0cSchristos if (rp->last == NULL)
249*6167eca2Schristos mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
2505c413d0cSchristos else if (rp->last->pos == TBL_CELL_HORIZ ||
2515c413d0cSchristos rp->last->pos == TBL_CELL_DHORIZ)
2525c413d0cSchristos c = rp->last->pos;
2535c413d0cSchristos } else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
254*6167eca2Schristos mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);
255c0d9444aSjoerg
256c0d9444aSjoerg (*pos)++;
257c0d9444aSjoerg
258c0d9444aSjoerg /* Allocate cell then parse its modifiers. */
259c0d9444aSjoerg
2605c413d0cSchristos mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
261c0d9444aSjoerg }
262c0d9444aSjoerg
2635c413d0cSchristos void
tbl_layout(struct tbl_node * tbl,int ln,const char * p,int pos)2645c413d0cSchristos tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
265c0d9444aSjoerg {
266c0d9444aSjoerg struct tbl_row *rp;
267c0d9444aSjoerg
2685c413d0cSchristos rp = NULL;
2695c413d0cSchristos for (;;) {
2705c413d0cSchristos /* Skip whitespace before and after each cell. */
2715c413d0cSchristos
2725c413d0cSchristos while (p[pos] == ' ' || p[pos] == '\t')
2735c413d0cSchristos pos++;
2745c413d0cSchristos
2755c413d0cSchristos switch (p[pos]) {
2765c413d0cSchristos case ',': /* Next row on this input line. */
2775c413d0cSchristos pos++;
2785c413d0cSchristos rp = NULL;
2795c413d0cSchristos continue;
2805c413d0cSchristos case '\0': /* Next row on next input line. */
2815c413d0cSchristos return;
2825c413d0cSchristos case '.': /* End of layout. */
2835c413d0cSchristos pos++;
2845c413d0cSchristos tbl->part = TBL_PART_DATA;
2855c413d0cSchristos
2865c413d0cSchristos /*
2875c413d0cSchristos * When the layout is completely empty,
2885c413d0cSchristos * default to one left-justified column.
289c0d9444aSjoerg */
290c0d9444aSjoerg
2915c413d0cSchristos if (tbl->first_row == NULL) {
2925c413d0cSchristos tbl->first_row = tbl->last_row =
2935c413d0cSchristos mandoc_calloc(1, sizeof(*rp));
2945c413d0cSchristos }
2955c413d0cSchristos if (tbl->first_row->first == NULL) {
2965c413d0cSchristos mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
297*6167eca2Schristos ln, pos, NULL);
2985c413d0cSchristos cell_alloc(tbl, tbl->first_row,
2995c413d0cSchristos TBL_CELL_LEFT);
30014e7489eSchristos if (tbl->opts.lvert < tbl->first_row->vert)
30114e7489eSchristos tbl->opts.lvert = tbl->first_row->vert;
3025c413d0cSchristos return;
3035c413d0cSchristos }
3045c413d0cSchristos
3055c413d0cSchristos /*
3065c413d0cSchristos * Search for the widest line
3075c413d0cSchristos * along the left and right margins.
3085c413d0cSchristos */
3095c413d0cSchristos
3105c413d0cSchristos for (rp = tbl->first_row; rp; rp = rp->next) {
3115c413d0cSchristos if (tbl->opts.lvert < rp->vert)
3125c413d0cSchristos tbl->opts.lvert = rp->vert;
3135c413d0cSchristos if (rp->last != NULL &&
3145c413d0cSchristos rp->last->col + 1 == tbl->opts.cols &&
3155c413d0cSchristos tbl->opts.rvert < rp->last->vert)
3165c413d0cSchristos tbl->opts.rvert = rp->last->vert;
3175c413d0cSchristos
3185c413d0cSchristos /* If the last line is empty, drop it. */
3195c413d0cSchristos
3205c413d0cSchristos if (rp->next != NULL &&
3215c413d0cSchristos rp->next->first == NULL) {
3225c413d0cSchristos free(rp->next);
3235c413d0cSchristos rp->next = NULL;
324f47368cfSchristos tbl->last_row = rp;
3255c413d0cSchristos }
3265c413d0cSchristos }
3275c413d0cSchristos return;
3285c413d0cSchristos default: /* Cell. */
3295c413d0cSchristos break;
3305c413d0cSchristos }
3315c413d0cSchristos
3325c413d0cSchristos /*
3335c413d0cSchristos * If the last line had at least one cell,
3345c413d0cSchristos * start a new one; otherwise, continue it.
3355c413d0cSchristos */
3365c413d0cSchristos
3375c413d0cSchristos if (rp == NULL) {
3385c413d0cSchristos if (tbl->last_row == NULL ||
3395c413d0cSchristos tbl->last_row->first != NULL) {
3405c413d0cSchristos rp = mandoc_calloc(1, sizeof(*rp));
341603fc4ebSjoerg if (tbl->last_row)
342c0d9444aSjoerg tbl->last_row->next = rp;
343603fc4ebSjoerg else
344603fc4ebSjoerg tbl->first_row = rp;
345c0d9444aSjoerg tbl->last_row = rp;
3465c413d0cSchristos } else
3475c413d0cSchristos rp = tbl->last_row;
348c0d9444aSjoerg }
3495c413d0cSchristos cell(tbl, rp, ln, p, &pos);
350c0d9444aSjoerg }
351c0d9444aSjoerg }
352c0d9444aSjoerg
353c0d9444aSjoerg static struct tbl_cell *
cell_alloc(struct tbl_node * tbl,struct tbl_row * rp,enum tbl_cellt pos)3545c413d0cSchristos cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
355c0d9444aSjoerg {
356c0d9444aSjoerg struct tbl_cell *p, *pp;
357c0d9444aSjoerg
3585c413d0cSchristos p = mandoc_calloc(1, sizeof(*p));
35914e7489eSchristos p->spacing = SIZE_MAX;
3605c413d0cSchristos p->pos = pos;
361c0d9444aSjoerg
3625c413d0cSchristos if ((pp = rp->last) != NULL) {
363603fc4ebSjoerg pp->next = p;
3645c413d0cSchristos p->col = pp->col + 1;
3655c413d0cSchristos } else
366603fc4ebSjoerg rp->first = p;
367c0d9444aSjoerg rp->last = p;
368c0d9444aSjoerg
3695c413d0cSchristos if (tbl->opts.cols <= p->col)
3705c413d0cSchristos tbl->opts.cols = p->col + 1;
371c0d9444aSjoerg
372f47368cfSchristos return p;
373c0d9444aSjoerg }
374