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