1*60395358Sschwarze /* $OpenBSD: tbl_layout.c,v 1.38 2025/01/05 18:03:51 schwarze Exp $ */ 2393cb51eSschwarze /* 3*60395358Sschwarze * Copyright (c) 2012, 2014, 2015, 2017, 2020, 2021, 2025 47d063611Sschwarze * Ingo Schwarze <schwarze@openbsd.org> 5*60395358Sschwarze * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 6393cb51eSschwarze * 7393cb51eSschwarze * Permission to use, copy, modify, and distribute this software for any 8393cb51eSschwarze * purpose with or without fee is hereby granted, provided that the above 9393cb51eSschwarze * copyright notice and this permission notice appear in all copies. 10393cb51eSschwarze * 11393cb51eSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12393cb51eSschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13393cb51eSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14393cb51eSschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15393cb51eSschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16393cb51eSschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17393cb51eSschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18393cb51eSschwarze */ 1946fa2066Sschwarze #include <sys/types.h> 2046fa2066Sschwarze 21393cb51eSschwarze #include <ctype.h> 22e8a65004Sschwarze #include <stdint.h> 23e501e731Sschwarze #include <stdio.h> 24393cb51eSschwarze #include <stdlib.h> 25393cb51eSschwarze #include <string.h> 262791bd1cSschwarze #include <time.h> 27393cb51eSschwarze 284f4f7972Sschwarze #include "mandoc_aux.h" 29fae2491eSschwarze #include "mandoc.h" 30fae2491eSschwarze #include "tbl.h" 312791bd1cSschwarze #include "libmandoc.h" 32fb382a01Sschwarze #include "tbl_int.h" 33393cb51eSschwarze 34393cb51eSschwarze struct tbl_phrase { 35393cb51eSschwarze char name; 36393cb51eSschwarze enum tbl_cellt key; 37393cb51eSschwarze }; 38393cb51eSschwarze 39bba1fa43Sschwarze static const struct tbl_phrase keys[] = { 40393cb51eSschwarze { 'c', TBL_CELL_CENTRE }, 41393cb51eSschwarze { 'r', TBL_CELL_RIGHT }, 42393cb51eSschwarze { 'l', TBL_CELL_LEFT }, 43393cb51eSschwarze { 'n', TBL_CELL_NUMBER }, 44393cb51eSschwarze { 's', TBL_CELL_SPAN }, 45393cb51eSschwarze { 'a', TBL_CELL_LONG }, 46393cb51eSschwarze { '^', TBL_CELL_DOWN }, 47393cb51eSschwarze { '-', TBL_CELL_HORIZ }, 48393cb51eSschwarze { '_', TBL_CELL_HORIZ }, 493006448aSschwarze { '=', TBL_CELL_DHORIZ } 50393cb51eSschwarze }; 51393cb51eSschwarze 52bba1fa43Sschwarze #define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0]))) 53bba1fa43Sschwarze 54bba1fa43Sschwarze static void mods(struct tbl_node *, struct tbl_cell *, 55393cb51eSschwarze int, const char *, int *); 56bba1fa43Sschwarze static void cell(struct tbl_node *, struct tbl_row *, 572791bd1cSschwarze int, const char *, int *); 583006448aSschwarze static struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *, 59fd9b947eSschwarze enum tbl_cellt); 60393cb51eSschwarze 6149aff9f8Sschwarze 62bba1fa43Sschwarze static void 632791bd1cSschwarze mods(struct tbl_node *tbl, struct tbl_cell *cp, 642791bd1cSschwarze int ln, const char *p, int *pos) 65393cb51eSschwarze { 66bba1fa43Sschwarze char *endptr; 67c107dca7Sschwarze unsigned long spacing; 687d063611Sschwarze int isz; 697d063611Sschwarze enum mandoc_esc fontesc; 70a5e11edeSschwarze 712791bd1cSschwarze mod: 72bba1fa43Sschwarze while (p[*pos] == ' ' || p[*pos] == '\t') 73bba1fa43Sschwarze (*pos)++; 74bba1fa43Sschwarze 75bba1fa43Sschwarze /* Row delimiters and cell specifiers end modifier lists. */ 76bba1fa43Sschwarze 77fd9b947eSschwarze if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL) 78bba1fa43Sschwarze return; 79393cb51eSschwarze 80ec04407bSschwarze /* Throw away parenthesised expression. */ 81ec04407bSschwarze 82ec04407bSschwarze if ('(' == p[*pos]) { 83ec04407bSschwarze (*pos)++; 84ec04407bSschwarze while (p[*pos] && ')' != p[*pos]) 85ec04407bSschwarze (*pos)++; 86ec04407bSschwarze if (')' == p[*pos]) { 87ec04407bSschwarze (*pos)++; 88ec04407bSschwarze goto mod; 89ec04407bSschwarze } 90a5a5f808Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL); 91bba1fa43Sschwarze return; 92ec04407bSschwarze } 93ec04407bSschwarze 94393cb51eSschwarze /* Parse numerical spacing from modifier string. */ 95393cb51eSschwarze 962791bd1cSschwarze if (isdigit((unsigned char)p[*pos])) { 97c107dca7Sschwarze if ((spacing = strtoul(p + *pos, &endptr, 10)) > 9) 98c107dca7Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_SPC, ln, *pos, 99c107dca7Sschwarze "%lu", spacing); 100c107dca7Sschwarze else 101c107dca7Sschwarze cp->spacing = spacing; 102bba1fa43Sschwarze *pos = endptr - p; 1032791bd1cSschwarze goto mod; 104393cb51eSschwarze } 105393cb51eSschwarze 106ec04407bSschwarze switch (tolower((unsigned char)p[(*pos)++])) { 107bba1fa43Sschwarze case 'b': 1087d063611Sschwarze cp->font = ESCAPE_FONTBOLD; 109897e9af6Sschwarze goto mod; 110bba1fa43Sschwarze case 'd': 111bba1fa43Sschwarze cp->flags |= TBL_CELL_BALIGN; 1122791bd1cSschwarze goto mod; 11349aff9f8Sschwarze case 'e': 114393cb51eSschwarze cp->flags |= TBL_CELL_EQUAL; 1152791bd1cSschwarze goto mod; 116bba1fa43Sschwarze case 'f': 117bba1fa43Sschwarze break; 118897e9af6Sschwarze case 'i': 1197d063611Sschwarze cp->font = ESCAPE_FONTITALIC; 120897e9af6Sschwarze goto mod; 121bba1fa43Sschwarze case 'm': 122a5a5f808Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m"); 123bba1fa43Sschwarze goto mod; 124bba1fa43Sschwarze case 'p': 125bba1fa43Sschwarze case 'v': 126bba1fa43Sschwarze if (p[*pos] == '-' || p[*pos] == '+') 127bba1fa43Sschwarze (*pos)++; 128bba1fa43Sschwarze while (isdigit((unsigned char)p[*pos])) 129bba1fa43Sschwarze (*pos)++; 130bba1fa43Sschwarze goto mod; 13149aff9f8Sschwarze case 't': 132393cb51eSschwarze cp->flags |= TBL_CELL_TALIGN; 1332791bd1cSschwarze goto mod; 134bba1fa43Sschwarze case 'u': 135bba1fa43Sschwarze cp->flags |= TBL_CELL_UP; 1362791bd1cSschwarze goto mod; 1372c3e66c4Sschwarze case 'w': 1382c3e66c4Sschwarze if (p[*pos] == '(') { 1392c3e66c4Sschwarze (*pos)++; 140*60395358Sschwarze isz = 0; 141*60395358Sschwarze if (roff_evalnum(ln, p, pos, &isz, 'n', 1) == 0 || 142*60395358Sschwarze p[*pos] != ')') 143*60395358Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_WIDTH, 144*60395358Sschwarze ln, *pos, "%s", p + *pos); 145*60395358Sschwarze else { 146*60395358Sschwarze /* Convert from BU to EN and round. */ 147*60395358Sschwarze cp->width = (isz + 11) /24; 1482c3e66c4Sschwarze (*pos)++; 1492c3e66c4Sschwarze } 150*60395358Sschwarze } else { 151*60395358Sschwarze cp->width = 0; 152*60395358Sschwarze while (isdigit((unsigned char)p[*pos])) { 153*60395358Sschwarze cp->width *= 10; 154*60395358Sschwarze cp->width += p[(*pos)++] - '0'; 155*60395358Sschwarze } 156*60395358Sschwarze if (cp->width == 0) 157*60395358Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_WIDTH, 158*60395358Sschwarze ln, *pos, "%s", p + *pos); 159*60395358Sschwarze } 160a3dd34c5Sschwarze goto mod; 16146fa2066Sschwarze case 'x': 16246fa2066Sschwarze cp->flags |= TBL_CELL_WMAX; 16346fa2066Sschwarze goto mod; 164bba1fa43Sschwarze case 'z': 165bba1fa43Sschwarze cp->flags |= TBL_CELL_WIGN; 166bba1fa43Sschwarze goto mod; 167fd9b947eSschwarze case '|': 168fd9b947eSschwarze if (cp->vert < 2) 169fd9b947eSschwarze cp->vert++; 170fd9b947eSschwarze else 171fd9b947eSschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_VERT, 172a5a5f808Sschwarze ln, *pos - 1, NULL); 173fd9b947eSschwarze goto mod; 174393cb51eSschwarze default: 175a5a5f808Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR, 176bba1fa43Sschwarze ln, *pos - 1, "%c", p[*pos - 1]); 177bba1fa43Sschwarze goto mod; 178393cb51eSschwarze } 179393cb51eSschwarze 1807d063611Sschwarze while (p[*pos] == ' ' || p[*pos] == '\t') 1817d063611Sschwarze (*pos)++; 1827d063611Sschwarze 183897e9af6Sschwarze /* Ignore parenthised font names for now. */ 184897e9af6Sschwarze 185897e9af6Sschwarze if (p[*pos] == '(') 186897e9af6Sschwarze goto mod; 187897e9af6Sschwarze 1887d063611Sschwarze isz = 0; 1897d063611Sschwarze if (p[*pos] != '\0') 1907d063611Sschwarze isz++; 1917d063611Sschwarze if (strchr(" \t.", p[*pos + isz]) == NULL) 1927d063611Sschwarze isz++; 193897e9af6Sschwarze 1947d063611Sschwarze fontesc = mandoc_font(p + *pos, isz); 1957d063611Sschwarze 1967d063611Sschwarze switch (fontesc) { 1977d063611Sschwarze case ESCAPE_FONTPREV: 1987d063611Sschwarze case ESCAPE_ERROR: 199a5a5f808Sschwarze mandoc_msg(MANDOCERR_FT_BAD, 200897e9af6Sschwarze ln, *pos, "TS %s", p + *pos - 1); 2017d063611Sschwarze break; 202393cb51eSschwarze default: 2037d063611Sschwarze cp->font = fontesc; 2047d063611Sschwarze break; 2052fc8b5b2Sschwarze } 2067d063611Sschwarze *pos += isz; 2077d063611Sschwarze goto mod; 208393cb51eSschwarze } 209393cb51eSschwarze 210bba1fa43Sschwarze static void 2112791bd1cSschwarze cell(struct tbl_node *tbl, struct tbl_row *rp, 2122791bd1cSschwarze int ln, const char *p, int *pos) 213393cb51eSschwarze { 214fd9b947eSschwarze int i; 215393cb51eSschwarze enum tbl_cellt c; 216393cb51eSschwarze 217fd9b947eSschwarze /* Handle leading vertical lines */ 2183006448aSschwarze 219bba1fa43Sschwarze while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') { 220bba1fa43Sschwarze if (p[*pos] == '|') { 221fd9b947eSschwarze if (rp->vert < 2) 222fd9b947eSschwarze rp->vert++; 223bba1fa43Sschwarze else 224bba1fa43Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_VERT, 225a5a5f808Sschwarze ln, *pos, NULL); 226bba1fa43Sschwarze } 2273006448aSschwarze (*pos)++; 228bba1fa43Sschwarze } 2293006448aSschwarze 230fd9b947eSschwarze again: 231fd9b947eSschwarze while (p[*pos] == ' ' || p[*pos] == '\t') 232fd9b947eSschwarze (*pos)++; 23392b16df0Sschwarze 234fd9b947eSschwarze if (p[*pos] == '.' || p[*pos] == '\0') 235bba1fa43Sschwarze return; 23692b16df0Sschwarze 2373006448aSschwarze /* Parse the column position (`c', `l', `r', ...). */ 238393cb51eSschwarze 2392791bd1cSschwarze for (i = 0; i < KEYS_MAX; i++) 240ec04407bSschwarze if (tolower((unsigned char)p[*pos]) == keys[i].name) 241393cb51eSschwarze break; 2422791bd1cSschwarze 243bba1fa43Sschwarze if (i == KEYS_MAX) { 244a5a5f808Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR, 245bba1fa43Sschwarze ln, *pos, "%c", p[*pos]); 246bba1fa43Sschwarze (*pos)++; 247bba1fa43Sschwarze goto again; 248393cb51eSschwarze } 2492791bd1cSschwarze c = keys[i].key; 250393cb51eSschwarze 251bba1fa43Sschwarze /* Special cases of spanners. */ 252ec04407bSschwarze 253bba1fa43Sschwarze if (c == TBL_CELL_SPAN) { 254bba1fa43Sschwarze if (rp->last == NULL) 255a5a5f808Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL); 256bba1fa43Sschwarze else if (rp->last->pos == TBL_CELL_HORIZ || 257bba1fa43Sschwarze rp->last->pos == TBL_CELL_DHORIZ) 258bba1fa43Sschwarze c = rp->last->pos; 259bba1fa43Sschwarze } else if (c == TBL_CELL_DOWN && rp == tbl->first_row) 260a5a5f808Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL); 261ec04407bSschwarze 262ec04407bSschwarze (*pos)++; 263ec04407bSschwarze 264393cb51eSschwarze /* Allocate cell then parse its modifiers. */ 265393cb51eSschwarze 266fd9b947eSschwarze mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos); 267393cb51eSschwarze } 268393cb51eSschwarze 269dca985f9Sschwarze void 27039aede77Sschwarze tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos) 271393cb51eSschwarze { 272393cb51eSschwarze struct tbl_row *rp; 273393cb51eSschwarze 27400038a64Sschwarze rp = NULL; 27500038a64Sschwarze for (;;) { 27600038a64Sschwarze /* Skip whitespace before and after each cell. */ 27700038a64Sschwarze 278bba1fa43Sschwarze while (p[pos] == ' ' || p[pos] == '\t') 27900038a64Sschwarze pos++; 28000038a64Sschwarze 28100038a64Sschwarze switch (p[pos]) { 28200038a64Sschwarze case ',': /* Next row on this input line. */ 28300038a64Sschwarze pos++; 28400038a64Sschwarze rp = NULL; 28500038a64Sschwarze continue; 28600038a64Sschwarze case '\0': /* Next row on next input line. */ 287dca985f9Sschwarze return; 28800038a64Sschwarze case '.': /* End of layout. */ 28900038a64Sschwarze pos++; 29000038a64Sschwarze tbl->part = TBL_PART_DATA; 291fd9b947eSschwarze 292fd9b947eSschwarze /* 293fd9b947eSschwarze * When the layout is completely empty, 294fd9b947eSschwarze * default to one left-justified column. 295fd9b947eSschwarze */ 296fd9b947eSschwarze 297fd9b947eSschwarze if (tbl->first_row == NULL) { 298b6434279Sschwarze tbl->first_row = tbl->last_row = 299b6434279Sschwarze mandoc_calloc(1, sizeof(*rp)); 300b6434279Sschwarze } 301b6434279Sschwarze if (tbl->first_row->first == NULL) { 302bba1fa43Sschwarze mandoc_msg(MANDOCERR_TBLLAYOUT_NONE, 303a5a5f808Sschwarze ln, pos, NULL); 304b6434279Sschwarze cell_alloc(tbl, tbl->first_row, 305b6434279Sschwarze TBL_CELL_LEFT); 3066a8d9d41Sschwarze if (tbl->opts.lvert < tbl->first_row->vert) 3076a8d9d41Sschwarze tbl->opts.lvert = tbl->first_row->vert; 308dca985f9Sschwarze return; 309fd9b947eSschwarze } 310fd9b947eSschwarze 311fd9b947eSschwarze /* 312fd9b947eSschwarze * Search for the widest line 313fd9b947eSschwarze * along the left and right margins. 314fd9b947eSschwarze */ 315fd9b947eSschwarze 316fd9b947eSschwarze for (rp = tbl->first_row; rp; rp = rp->next) { 317fd9b947eSschwarze if (tbl->opts.lvert < rp->vert) 318fd9b947eSschwarze tbl->opts.lvert = rp->vert; 319fd9b947eSschwarze if (rp->last != NULL && 3205f6d1ba3Sschwarze rp->last->col + 1 == tbl->opts.cols && 321fd9b947eSschwarze tbl->opts.rvert < rp->last->vert) 322fd9b947eSschwarze tbl->opts.rvert = rp->last->vert; 323b6434279Sschwarze 324b6434279Sschwarze /* If the last line is empty, drop it. */ 325b6434279Sschwarze 326b6434279Sschwarze if (rp->next != NULL && 327b6434279Sschwarze rp->next->first == NULL) { 328b6434279Sschwarze free(rp->next); 329b6434279Sschwarze rp->next = NULL; 33016f08be4Sschwarze tbl->last_row = rp; 331b6434279Sschwarze } 332fd9b947eSschwarze } 333fd9b947eSschwarze return; 33400038a64Sschwarze default: /* Cell. */ 33500038a64Sschwarze break; 33600038a64Sschwarze } 33700038a64Sschwarze 338b6434279Sschwarze /* 339b6434279Sschwarze * If the last line had at least one cell, 340b6434279Sschwarze * start a new one; otherwise, continue it. 341b6434279Sschwarze */ 342b6434279Sschwarze 343b6434279Sschwarze if (rp == NULL) { 344b6434279Sschwarze if (tbl->last_row == NULL || 345b6434279Sschwarze tbl->last_row->first != NULL) { 34600038a64Sschwarze rp = mandoc_calloc(1, sizeof(*rp)); 3473006448aSschwarze if (tbl->last_row) 3482791bd1cSschwarze tbl->last_row->next = rp; 3493006448aSschwarze else 3503006448aSschwarze tbl->first_row = rp; 3512791bd1cSschwarze tbl->last_row = rp; 352b6434279Sschwarze } else 353b6434279Sschwarze rp = tbl->last_row; 354393cb51eSschwarze } 355bba1fa43Sschwarze cell(tbl, rp, ln, p, &pos); 356393cb51eSschwarze } 35700038a64Sschwarze } 3582791bd1cSschwarze 3592791bd1cSschwarze static struct tbl_cell * 360fd9b947eSschwarze cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos) 3612791bd1cSschwarze { 3622791bd1cSschwarze struct tbl_cell *p, *pp; 3632791bd1cSschwarze 36421da0636Sschwarze p = mandoc_calloc(1, sizeof(*p)); 365e8a65004Sschwarze p->spacing = SIZE_MAX; 3667d063611Sschwarze p->font = ESCAPE_FONTROMAN; 3675f6d1ba3Sschwarze p->pos = pos; 3682791bd1cSschwarze 36921da0636Sschwarze if ((pp = rp->last) != NULL) { 3703006448aSschwarze pp->next = p; 3715f6d1ba3Sschwarze p->col = pp->col + 1; 3725f6d1ba3Sschwarze } else 3733006448aSschwarze rp->first = p; 3742791bd1cSschwarze rp->last = p; 3752791bd1cSschwarze 3765f6d1ba3Sschwarze if (tbl->opts.cols <= p->col) 3775f6d1ba3Sschwarze tbl->opts.cols = p->col + 1; 3782791bd1cSschwarze 379526e306bSschwarze return p; 3802791bd1cSschwarze } 381