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