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