Annotation of src/usr.bin/mandoc/man_term.c, Revision 1.166
1.166 ! schwarze 1: /* $OpenBSD: man_term.c,v 1.165 2018/08/14 01:26:12 schwarze Exp $ */
1.1 kristaps 2: /*
1.84 schwarze 3: * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
1.163 schwarze 4: * Copyright (c) 2010-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
1.2 schwarze 7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
1.1 kristaps 9: *
1.124 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.2 schwarze 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.124 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.2 schwarze 13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.1 kristaps 17: */
1.13 schwarze 18: #include <sys/types.h>
19:
1.1 kristaps 20: #include <assert.h>
1.11 schwarze 21: #include <ctype.h>
1.117 schwarze 22: #include <limits.h>
1.1 kristaps 23: #include <stdio.h>
24: #include <stdlib.h>
25: #include <string.h>
26:
1.124 schwarze 27: #include "mandoc_aux.h"
1.37 schwarze 28: #include "mandoc.h"
1.124 schwarze 29: #include "roff.h"
30: #include "man.h"
1.18 schwarze 31: #include "out.h"
1.1 kristaps 32: #include "term.h"
1.18 schwarze 33: #include "main.h"
1.10 schwarze 34:
1.70 schwarze 35: #define MAXMARGINS 64 /* maximum number of indented scopes */
1.1 kristaps 36:
1.11 schwarze 37: struct mtermp {
38: int fl;
39: #define MANT_LITERAL (1 << 0)
1.116 schwarze 40: int lmargin[MAXMARGINS]; /* margins (incl. vis. page) */
1.70 schwarze 41: int lmargincur; /* index of current margin */
42: int lmarginsz; /* actual number of nested margins */
43: size_t offset; /* default offset to visible page */
1.88 schwarze 44: int pardist; /* vert. space before par., unit: [v] */
1.11 schwarze 45: };
46:
1.100 schwarze 47: #define DECL_ARGS struct termp *p, \
1.11 schwarze 48: struct mtermp *mt, \
1.125 schwarze 49: struct roff_node *n, \
1.126 schwarze 50: const struct roff_meta *meta
1.1 kristaps 51:
52: struct termact {
53: int (*pre)(DECL_ARGS);
54: void (*post)(DECL_ARGS);
1.26 schwarze 55: int flags;
56: #define MAN_NOTEXT (1 << 0) /* Never has text children. */
1.1 kristaps 57: };
58:
1.21 schwarze 59: static void print_man_nodelist(DECL_ARGS);
1.19 schwarze 60: static void print_man_node(DECL_ARGS);
1.126 schwarze 61: static void print_man_head(struct termp *,
62: const struct roff_meta *);
63: static void print_man_foot(struct termp *,
64: const struct roff_meta *);
1.100 schwarze 65: static void print_bvspace(struct termp *,
1.125 schwarze 66: const struct roff_node *, int);
1.18 schwarze 67:
1.1 kristaps 68: static int pre_B(DECL_ARGS);
1.152 schwarze 69: static int pre_DT(DECL_ARGS);
1.11 schwarze 70: static int pre_HP(DECL_ARGS);
1.1 kristaps 71: static int pre_I(DECL_ARGS);
72: static int pre_IP(DECL_ARGS);
1.82 schwarze 73: static int pre_OP(DECL_ARGS);
1.88 schwarze 74: static int pre_PD(DECL_ARGS);
1.1 kristaps 75: static int pre_PP(DECL_ARGS);
1.13 schwarze 76: static int pre_RS(DECL_ARGS);
1.1 kristaps 77: static int pre_SH(DECL_ARGS);
78: static int pre_SS(DECL_ARGS);
79: static int pre_TP(DECL_ARGS);
1.91 schwarze 80: static int pre_UR(DECL_ARGS);
1.82 schwarze 81: static int pre_alternate(DECL_ARGS);
1.14 schwarze 82: static int pre_ign(DECL_ARGS);
1.45 schwarze 83: static int pre_in(DECL_ARGS);
84: static int pre_literal(DECL_ARGS);
1.1 kristaps 85:
1.11 schwarze 86: static void post_IP(DECL_ARGS);
87: static void post_HP(DECL_ARGS);
1.13 schwarze 88: static void post_RS(DECL_ARGS);
1.1 kristaps 89: static void post_SH(DECL_ARGS);
90: static void post_SS(DECL_ARGS);
1.11 schwarze 91: static void post_TP(DECL_ARGS);
1.91 schwarze 92: static void post_UR(DECL_ARGS);
1.1 kristaps 93:
1.145 schwarze 94: static const struct termact __termacts[MAN_MAX - MAN_TH] = {
1.26 schwarze 95: { NULL, NULL, 0 }, /* TH */
96: { pre_SH, post_SH, 0 }, /* SH */
97: { pre_SS, post_SS, 0 }, /* SS */
98: { pre_TP, post_TP, 0 }, /* TP */
1.166 ! schwarze 99: { pre_TP, post_TP, 0 }, /* TQ */
1.26 schwarze 100: { pre_PP, NULL, 0 }, /* LP */
101: { pre_PP, NULL, 0 }, /* PP */
102: { pre_PP, NULL, 0 }, /* P */
103: { pre_IP, post_IP, 0 }, /* IP */
1.100 schwarze 104: { pre_HP, post_HP, 0 }, /* HP */
1.26 schwarze 105: { NULL, NULL, 0 }, /* SM */
106: { pre_B, NULL, 0 }, /* SB */
1.51 schwarze 107: { pre_alternate, NULL, 0 }, /* BI */
108: { pre_alternate, NULL, 0 }, /* IB */
109: { pre_alternate, NULL, 0 }, /* BR */
110: { pre_alternate, NULL, 0 }, /* RB */
1.26 schwarze 111: { NULL, NULL, 0 }, /* R */
112: { pre_B, NULL, 0 }, /* B */
113: { pre_I, NULL, 0 }, /* I */
1.51 schwarze 114: { pre_alternate, NULL, 0 }, /* IR */
115: { pre_alternate, NULL, 0 }, /* RI */
1.45 schwarze 116: { pre_literal, NULL, 0 }, /* nf */
117: { pre_literal, NULL, 0 }, /* fi */
1.26 schwarze 118: { NULL, NULL, 0 }, /* RE */
119: { pre_RS, post_RS, 0 }, /* RS */
1.152 schwarze 120: { pre_DT, NULL, 0 }, /* DT */
1.111 schwarze 121: { pre_ign, NULL, MAN_NOTEXT }, /* UC */
1.88 schwarze 122: { pre_PD, NULL, MAN_NOTEXT }, /* PD */
1.36 schwarze 123: { pre_ign, NULL, 0 }, /* AT */
1.45 schwarze 124: { pre_in, NULL, MAN_NOTEXT }, /* in */
1.82 schwarze 125: { pre_OP, NULL, 0 }, /* OP */
1.83 schwarze 126: { pre_literal, NULL, 0 }, /* EX */
127: { pre_literal, NULL, 0 }, /* EE */
1.91 schwarze 128: { pre_UR, post_UR, 0 }, /* UR */
129: { NULL, NULL, 0 }, /* UE */
1.161 bentley 130: { pre_UR, post_UR, 0 }, /* MT */
131: { NULL, NULL, 0 }, /* ME */
1.1 kristaps 132: };
1.145 schwarze 133: static const struct termact *termacts = __termacts - MAN_TH;
1.11 schwarze 134:
1.1 kristaps 135:
1.16 schwarze 136: void
1.133 schwarze 137: terminal_man(void *arg, const struct roff_man *man)
1.1 kristaps 138: {
1.18 schwarze 139: struct termp *p;
1.125 schwarze 140: struct roff_node *n;
1.18 schwarze 141: struct mtermp mt;
1.142 schwarze 142: size_t save_defindent;
1.18 schwarze 143:
144: p = (struct termp *)arg;
1.159 schwarze 145: save_defindent = p->defindent;
146: if (p->synopsisonly == 0 && p->defindent == 0)
147: p->defindent = 7;
1.156 schwarze 148: p->tcol->rmargin = p->maxrmargin = p->defrmargin;
1.151 schwarze 149: term_tab_set(p, NULL);
150: term_tab_set(p, "T");
151: term_tab_set(p, ".5i");
1.18 schwarze 152:
1.70 schwarze 153: memset(&mt, 0, sizeof(struct mtermp));
1.77 schwarze 154: mt.lmargin[mt.lmargincur] = term_len(p, p->defindent);
155: mt.offset = term_len(p, p->defindent);
1.88 schwarze 156: mt.pardist = 1;
1.11 schwarze 157:
1.134 schwarze 158: n = man->first->child;
1.104 schwarze 159: if (p->synopsisonly) {
160: while (n != NULL) {
161: if (n->tok == MAN_SH &&
1.124 schwarze 162: n->child->child->type == ROFFT_TEXT &&
1.104 schwarze 163: !strcmp(n->child->child->string, "SYNOPSIS")) {
164: if (n->child->next->child != NULL)
165: print_man_nodelist(p, &mt,
1.134 schwarze 166: n->child->next->child,
167: &man->meta);
1.104 schwarze 168: term_newln(p);
169: break;
170: }
171: n = n->next;
172: }
173: } else {
1.134 schwarze 174: term_begin(p, print_man_head, print_man_foot, &man->meta);
1.104 schwarze 175: p->flags |= TERMP_NOSPACE;
176: if (n != NULL)
1.134 schwarze 177: print_man_nodelist(p, &mt, n, &man->meta);
1.104 schwarze 178: term_end(p);
179: }
1.159 schwarze 180: p->defindent = save_defindent;
1.1 kristaps 181: }
182:
1.69 schwarze 183: /*
184: * Printing leading vertical space before a block.
185: * This is used for the paragraph macros.
186: * The rules are pretty simple, since there's very little nesting going
187: * on here. Basically, if we're the first within another block (SS/SH),
188: * then don't emit vertical space. If we are (RS), then do. If not the
189: * first, print it.
190: */
1.18 schwarze 191: static void
1.125 schwarze 192: print_bvspace(struct termp *p, const struct roff_node *n, int pardist)
1.18 schwarze 193: {
1.88 schwarze 194: int i;
1.69 schwarze 195:
1.18 schwarze 196: term_newln(p);
1.64 schwarze 197:
1.69 schwarze 198: if (n->body && n->body->child)
1.124 schwarze 199: if (n->body->child->type == ROFFT_TBL)
1.69 schwarze 200: return;
1.11 schwarze 201:
1.124 schwarze 202: if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
1.69 schwarze 203: if (NULL == n->prev)
204: return;
1.11 schwarze 205:
1.88 schwarze 206: for (i = 0; i < pardist; i++)
207: term_vspace(p);
1.14 schwarze 208: }
209:
1.100 schwarze 210:
1.14 schwarze 211: static int
212: pre_ign(DECL_ARGS)
213: {
214:
1.138 schwarze 215: return 0;
1.11 schwarze 216: }
217:
1.1 kristaps 218: static int
219: pre_I(DECL_ARGS)
220: {
221:
1.21 schwarze 222: term_fontrepl(p, TERMFONT_UNDER);
1.138 schwarze 223: return 1;
1.1 kristaps 224: }
225:
1.11 schwarze 226: static int
1.45 schwarze 227: pre_literal(DECL_ARGS)
1.11 schwarze 228: {
229:
1.45 schwarze 230: term_newln(p);
1.53 schwarze 231:
1.156 schwarze 232: if (n->tok == MAN_nf || n->tok == MAN_EX)
1.45 schwarze 233: mt->fl |= MANT_LITERAL;
1.53 schwarze 234: else
1.45 schwarze 235: mt->fl &= ~MANT_LITERAL;
236:
1.72 schwarze 237: /*
238: * Unlike .IP and .TP, .HP does not have a HEAD.
239: * So in case a second call to term_flushln() is needed,
240: * indentation has to be set up explicitly.
241: */
1.156 schwarze 242: if (n->parent->tok == MAN_HP && p->tcol->rmargin < p->maxrmargin) {
243: p->tcol->offset = p->tcol->rmargin;
244: p->tcol->rmargin = p->maxrmargin;
1.93 schwarze 245: p->trailspace = 0;
1.99 schwarze 246: p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
1.72 schwarze 247: p->flags |= TERMP_NOSPACE;
248: }
249:
1.138 schwarze 250: return 0;
1.11 schwarze 251: }
252:
253: static int
1.88 schwarze 254: pre_PD(DECL_ARGS)
255: {
1.113 schwarze 256: struct roffsu su;
1.88 schwarze 257:
258: n = n->child;
1.113 schwarze 259: if (n == NULL) {
1.88 schwarze 260: mt->pardist = 1;
1.138 schwarze 261: return 0;
1.88 schwarze 262: }
1.124 schwarze 263: assert(n->type == ROFFT_TEXT);
1.157 schwarze 264: if (a2roffsu(n->string, &su, SCALE_VS) != NULL)
1.113 schwarze 265: mt->pardist = term_vspan(p, &su);
1.138 schwarze 266: return 0;
1.88 schwarze 267: }
268:
269: static int
1.51 schwarze 270: pre_alternate(DECL_ARGS)
1.1 kristaps 271: {
1.51 schwarze 272: enum termfont font[2];
1.125 schwarze 273: struct roff_node *nn;
1.51 schwarze 274: int savelit, i;
1.1 kristaps 275:
1.51 schwarze 276: switch (n->tok) {
1.100 schwarze 277: case MAN_RB:
1.51 schwarze 278: font[0] = TERMFONT_NONE;
279: font[1] = TERMFONT_BOLD;
280: break;
1.100 schwarze 281: case MAN_RI:
1.51 schwarze 282: font[0] = TERMFONT_NONE;
283: font[1] = TERMFONT_UNDER;
284: break;
1.100 schwarze 285: case MAN_BR:
1.51 schwarze 286: font[0] = TERMFONT_BOLD;
287: font[1] = TERMFONT_NONE;
288: break;
1.100 schwarze 289: case MAN_BI:
1.51 schwarze 290: font[0] = TERMFONT_BOLD;
291: font[1] = TERMFONT_UNDER;
292: break;
1.100 schwarze 293: case MAN_IR:
1.51 schwarze 294: font[0] = TERMFONT_UNDER;
295: font[1] = TERMFONT_NONE;
296: break;
1.100 schwarze 297: case MAN_IB:
1.51 schwarze 298: font[0] = TERMFONT_UNDER;
299: font[1] = TERMFONT_BOLD;
300: break;
301: default:
302: abort();
303: }
1.17 schwarze 304:
1.51 schwarze 305: savelit = MANT_LITERAL & mt->fl;
306: mt->fl &= ~MANT_LITERAL;
1.17 schwarze 307:
1.51 schwarze 308: for (i = 0, nn = n->child; nn; nn = nn->next, i = 1 - i) {
309: term_fontrepl(p, font[i]);
310: if (savelit && NULL == nn->next)
311: mt->fl |= MANT_LITERAL;
1.132 schwarze 312: assert(nn->type == ROFFT_TEXT);
313: term_word(p, nn->string);
1.141 schwarze 314: if (nn->flags & NODE_EOS)
1.132 schwarze 315: p->flags |= TERMP_SENTENCE;
1.51 schwarze 316: if (nn->next)
1.1 kristaps 317: p->flags |= TERMP_NOSPACE;
318: }
1.17 schwarze 319:
1.138 schwarze 320: return 0;
1.1 kristaps 321: }
322:
323: static int
324: pre_B(DECL_ARGS)
325: {
326:
1.21 schwarze 327: term_fontrepl(p, TERMFONT_BOLD);
1.138 schwarze 328: return 1;
1.82 schwarze 329: }
330:
331: static int
332: pre_OP(DECL_ARGS)
333: {
334:
335: term_word(p, "[");
336: p->flags |= TERMP_NOSPACE;
337:
338: if (NULL != (n = n->child)) {
339: term_fontrepl(p, TERMFONT_BOLD);
340: term_word(p, n->string);
341: }
342: if (NULL != n && NULL != n->next) {
343: term_fontrepl(p, TERMFONT_UNDER);
344: term_word(p, n->next->string);
345: }
346:
347: term_fontrepl(p, TERMFONT_NONE);
348: p->flags |= TERMP_NOSPACE;
349: term_word(p, "]");
1.138 schwarze 350: return 0;
1.1 kristaps 351: }
352:
353: static int
1.45 schwarze 354: pre_in(DECL_ARGS)
1.11 schwarze 355: {
1.116 schwarze 356: struct roffsu su;
357: const char *cp;
1.45 schwarze 358: size_t v;
1.116 schwarze 359: int less;
1.45 schwarze 360:
361: term_newln(p);
362:
1.156 schwarze 363: if (n->child == NULL) {
364: p->tcol->offset = mt->offset;
1.138 schwarze 365: return 0;
1.45 schwarze 366: }
1.11 schwarze 367:
1.45 schwarze 368: cp = n->child->string;
369: less = 0;
1.11 schwarze 370:
1.45 schwarze 371: if ('-' == *cp)
372: less = -1;
373: else if ('+' == *cp)
374: less = 1;
375: else
376: cp--;
377:
1.157 schwarze 378: if (a2roffsu(++cp, &su, SCALE_EN) == NULL)
1.138 schwarze 379: return 0;
1.45 schwarze 380:
1.158 schwarze 381: v = term_hen(p, &su);
1.45 schwarze 382:
383: if (less < 0)
1.156 schwarze 384: p->tcol->offset -= p->tcol->offset > v ? v : p->tcol->offset;
1.45 schwarze 385: else if (less > 0)
1.156 schwarze 386: p->tcol->offset += v;
1.100 schwarze 387: else
1.156 schwarze 388: p->tcol->offset = v;
389: if (p->tcol->offset > SHRT_MAX)
390: p->tcol->offset = term_len(p, p->defindent);
1.8 schwarze 391:
1.152 schwarze 392: return 0;
393: }
394:
395: static int
396: pre_DT(DECL_ARGS)
397: {
398: term_tab_set(p, NULL);
399: term_tab_set(p, "T");
400: term_tab_set(p, ".5i");
1.138 schwarze 401: return 0;
1.8 schwarze 402: }
403:
404: static int
1.11 schwarze 405: pre_HP(DECL_ARGS)
406: {
1.116 schwarze 407: struct roffsu su;
1.125 schwarze 408: const struct roff_node *nn;
1.116 schwarze 409: int len;
1.11 schwarze 410:
411: switch (n->type) {
1.124 schwarze 412: case ROFFT_BLOCK:
1.88 schwarze 413: print_bvspace(p, n, mt->pardist);
1.138 schwarze 414: return 1;
1.124 schwarze 415: case ROFFT_BODY:
1.11 schwarze 416: break;
417: default:
1.138 schwarze 418: return 0;
1.11 schwarze 419: }
420:
1.84 schwarze 421: if ( ! (MANT_LITERAL & mt->fl)) {
1.99 schwarze 422: p->flags |= TERMP_NOBREAK | TERMP_BRIND;
1.93 schwarze 423: p->trailspace = 2;
1.84 schwarze 424: }
425:
1.11 schwarze 426: /* Calculate offset. */
427:
1.116 schwarze 428: if ((nn = n->parent->head->child) != NULL &&
1.157 schwarze 429: a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
1.158 schwarze 430: len = term_hen(p, &su);
1.117 schwarze 431: if (len < 0 && (size_t)(-len) > mt->offset)
432: len = -mt->offset;
433: else if (len > SHRT_MAX)
434: len = term_len(p, p->defindent);
1.116 schwarze 435: mt->lmargin[mt->lmargincur] = len;
436: } else
437: len = mt->lmargin[mt->lmargincur];
1.11 schwarze 438:
1.156 schwarze 439: p->tcol->offset = mt->offset;
440: p->tcol->rmargin = mt->offset + len;
1.138 schwarze 441: return 1;
1.11 schwarze 442: }
443:
444: static void
445: post_HP(DECL_ARGS)
446: {
447:
448: switch (n->type) {
1.124 schwarze 449: case ROFFT_BODY:
1.90 schwarze 450: term_newln(p);
1.127 schwarze 451:
452: /*
453: * Compatibility with a groff bug.
454: * The .HP macro uses the undocumented .tag request
455: * which causes a line break and cancels no-space
456: * mode even if there isn't any output.
457: */
458:
459: if (n->child == NULL)
460: term_vspace(p);
461:
1.99 schwarze 462: p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
1.93 schwarze 463: p->trailspace = 0;
1.156 schwarze 464: p->tcol->offset = mt->offset;
465: p->tcol->rmargin = p->maxrmargin;
1.11 schwarze 466: break;
467: default:
468: break;
469: }
470: }
471:
472: static int
1.1 kristaps 473: pre_PP(DECL_ARGS)
474: {
475:
1.11 schwarze 476: switch (n->type) {
1.124 schwarze 477: case ROFFT_BLOCK:
1.77 schwarze 478: mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
1.88 schwarze 479: print_bvspace(p, n, mt->pardist);
1.11 schwarze 480: break;
481: default:
1.156 schwarze 482: p->tcol->offset = mt->offset;
1.11 schwarze 483: break;
484: }
485:
1.138 schwarze 486: return n->type != ROFFT_HEAD;
1.1 kristaps 487: }
488:
489: static int
490: pre_IP(DECL_ARGS)
491: {
1.116 schwarze 492: struct roffsu su;
1.125 schwarze 493: const struct roff_node *nn;
1.116 schwarze 494: int len, savelit;
1.11 schwarze 495:
496: switch (n->type) {
1.124 schwarze 497: case ROFFT_BODY:
1.11 schwarze 498: p->flags |= TERMP_NOSPACE;
499: break;
1.124 schwarze 500: case ROFFT_HEAD:
1.11 schwarze 501: p->flags |= TERMP_NOBREAK;
1.93 schwarze 502: p->trailspace = 1;
1.11 schwarze 503: break;
1.124 schwarze 504: case ROFFT_BLOCK:
1.88 schwarze 505: print_bvspace(p, n, mt->pardist);
1.11 schwarze 506: /* FALLTHROUGH */
507: default:
1.138 schwarze 508: return 1;
1.11 schwarze 509: }
510:
1.57 schwarze 511: /* Calculate the offset from the optional second argument. */
1.116 schwarze 512: if ((nn = n->parent->head->child) != NULL &&
513: (nn = nn->next) != NULL &&
1.157 schwarze 514: a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
1.158 schwarze 515: len = term_hen(p, &su);
1.116 schwarze 516: if (len < 0 && (size_t)(-len) > mt->offset)
517: len = -mt->offset;
1.117 schwarze 518: else if (len > SHRT_MAX)
519: len = term_len(p, p->defindent);
520: mt->lmargin[mt->lmargincur] = len;
1.116 schwarze 521: } else
522: len = mt->lmargin[mt->lmargincur];
1.11 schwarze 523:
524: switch (n->type) {
1.124 schwarze 525: case ROFFT_HEAD:
1.156 schwarze 526: p->tcol->offset = mt->offset;
527: p->tcol->rmargin = mt->offset + len;
1.11 schwarze 528:
1.57 schwarze 529: savelit = MANT_LITERAL & mt->fl;
530: mt->fl &= ~MANT_LITERAL;
531:
532: if (n->child)
1.89 schwarze 533: print_man_node(p, mt, n->child, meta);
1.57 schwarze 534:
535: if (savelit)
536: mt->fl |= MANT_LITERAL;
537:
1.138 schwarze 538: return 0;
1.124 schwarze 539: case ROFFT_BODY:
1.156 schwarze 540: p->tcol->offset = mt->offset + len;
541: p->tcol->rmargin = p->maxrmargin;
1.11 schwarze 542: break;
543: default:
544: break;
545: }
1.1 kristaps 546:
1.138 schwarze 547: return 1;
1.11 schwarze 548: }
1.1 kristaps 549:
1.11 schwarze 550: static void
551: post_IP(DECL_ARGS)
552: {
1.4 schwarze 553:
1.11 schwarze 554: switch (n->type) {
1.124 schwarze 555: case ROFFT_HEAD:
1.11 schwarze 556: term_flushln(p);
557: p->flags &= ~TERMP_NOBREAK;
1.93 schwarze 558: p->trailspace = 0;
1.156 schwarze 559: p->tcol->rmargin = p->maxrmargin;
1.11 schwarze 560: break;
1.124 schwarze 561: case ROFFT_BODY:
1.57 schwarze 562: term_newln(p);
1.156 schwarze 563: p->tcol->offset = mt->offset;
1.11 schwarze 564: break;
565: default:
566: break;
567: }
1.1 kristaps 568: }
569:
570: static int
571: pre_TP(DECL_ARGS)
572: {
1.116 schwarze 573: struct roffsu su;
1.125 schwarze 574: struct roff_node *nn;
1.116 schwarze 575: int len, savelit;
1.11 schwarze 576:
577: switch (n->type) {
1.124 schwarze 578: case ROFFT_HEAD:
1.137 schwarze 579: p->flags |= TERMP_NOBREAK | TERMP_BRTRSP;
1.93 schwarze 580: p->trailspace = 1;
1.11 schwarze 581: break;
1.124 schwarze 582: case ROFFT_BODY:
1.11 schwarze 583: p->flags |= TERMP_NOSPACE;
584: break;
1.124 schwarze 585: case ROFFT_BLOCK:
1.166 ! schwarze 586: if (n->tok == MAN_TP)
! 587: print_bvspace(p, n, mt->pardist);
1.11 schwarze 588: /* FALLTHROUGH */
589: default:
1.138 schwarze 590: return 1;
1.11 schwarze 591: }
592:
593: /* Calculate offset. */
1.1 kristaps 594:
1.116 schwarze 595: if ((nn = n->parent->head->child) != NULL &&
1.141 schwarze 596: nn->string != NULL && ! (NODE_LINE & nn->flags) &&
1.157 schwarze 597: a2roffsu(nn->string, &su, SCALE_EN) != NULL) {
1.158 schwarze 598: len = term_hen(p, &su);
1.116 schwarze 599: if (len < 0 && (size_t)(-len) > mt->offset)
600: len = -mt->offset;
1.117 schwarze 601: else if (len > SHRT_MAX)
602: len = term_len(p, p->defindent);
603: mt->lmargin[mt->lmargincur] = len;
1.116 schwarze 604: } else
605: len = mt->lmargin[mt->lmargincur];
1.8 schwarze 606:
1.11 schwarze 607: switch (n->type) {
1.124 schwarze 608: case ROFFT_HEAD:
1.156 schwarze 609: p->tcol->offset = mt->offset;
610: p->tcol->rmargin = mt->offset + len;
1.11 schwarze 611:
1.57 schwarze 612: savelit = MANT_LITERAL & mt->fl;
613: mt->fl &= ~MANT_LITERAL;
614:
1.11 schwarze 615: /* Don't print same-line elements. */
1.95 schwarze 616: nn = n->child;
1.141 schwarze 617: while (NULL != nn && 0 == (NODE_LINE & nn->flags))
1.95 schwarze 618: nn = nn->next;
619:
620: while (NULL != nn) {
621: print_man_node(p, mt, nn, meta);
622: nn = nn->next;
623: }
1.11 schwarze 624:
1.57 schwarze 625: if (savelit)
626: mt->fl |= MANT_LITERAL;
1.138 schwarze 627: return 0;
1.124 schwarze 628: case ROFFT_BODY:
1.156 schwarze 629: p->tcol->offset = mt->offset + len;
630: p->tcol->rmargin = p->maxrmargin;
1.93 schwarze 631: p->trailspace = 0;
1.137 schwarze 632: p->flags &= ~(TERMP_NOBREAK | TERMP_BRTRSP);
1.11 schwarze 633: break;
634: default:
635: break;
636: }
1.1 kristaps 637:
1.138 schwarze 638: return 1;
1.11 schwarze 639: }
1.1 kristaps 640:
1.11 schwarze 641: static void
642: post_TP(DECL_ARGS)
643: {
1.1 kristaps 644:
1.11 schwarze 645: switch (n->type) {
1.124 schwarze 646: case ROFFT_HEAD:
1.11 schwarze 647: term_flushln(p);
648: break;
1.124 schwarze 649: case ROFFT_BODY:
1.57 schwarze 650: term_newln(p);
1.156 schwarze 651: p->tcol->offset = mt->offset;
1.11 schwarze 652: break;
653: default:
654: break;
655: }
1.1 kristaps 656: }
657:
658: static int
659: pre_SS(DECL_ARGS)
660: {
1.88 schwarze 661: int i;
1.1 kristaps 662:
1.11 schwarze 663: switch (n->type) {
1.124 schwarze 664: case ROFFT_BLOCK:
1.69 schwarze 665: mt->fl &= ~MANT_LITERAL;
1.77 schwarze 666: mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
667: mt->offset = term_len(p, p->defindent);
1.111 schwarze 668:
669: /*
670: * No vertical space before the first subsection
671: * and after an empty subsection.
672: */
673:
674: do {
675: n = n->prev;
1.162 schwarze 676: } while (n != NULL && n->tok >= MAN_TH &&
1.123 schwarze 677: termacts[n->tok].flags & MAN_NOTEXT);
1.163 schwarze 678: if (n == NULL || n->type == ROFFT_COMMENT ||
679: (n->tok == MAN_SS && n->body->child == NULL))
1.11 schwarze 680: break;
1.111 schwarze 681:
1.88 schwarze 682: for (i = 0; i < mt->pardist; i++)
683: term_vspace(p);
1.11 schwarze 684: break;
1.124 schwarze 685: case ROFFT_HEAD:
1.21 schwarze 686: term_fontrepl(p, TERMFONT_BOLD);
1.156 schwarze 687: p->tcol->offset = term_len(p, 3);
688: p->tcol->rmargin = mt->offset;
1.129 schwarze 689: p->trailspace = mt->offset;
690: p->flags |= TERMP_NOBREAK | TERMP_BRIND;
1.11 schwarze 691: break;
1.124 schwarze 692: case ROFFT_BODY:
1.156 schwarze 693: p->tcol->offset = mt->offset;
694: p->tcol->rmargin = p->maxrmargin;
1.129 schwarze 695: p->trailspace = 0;
696: p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
1.11 schwarze 697: break;
698: default:
699: break;
700: }
701:
1.138 schwarze 702: return 1;
1.1 kristaps 703: }
704:
705: static void
706: post_SS(DECL_ARGS)
707: {
1.100 schwarze 708:
1.11 schwarze 709: switch (n->type) {
1.124 schwarze 710: case ROFFT_HEAD:
1.11 schwarze 711: term_newln(p);
712: break;
1.124 schwarze 713: case ROFFT_BODY:
1.11 schwarze 714: term_newln(p);
715: break;
716: default:
717: break;
718: }
1.1 kristaps 719: }
720:
721: static int
722: pre_SH(DECL_ARGS)
723: {
1.88 schwarze 724: int i;
1.1 kristaps 725:
1.11 schwarze 726: switch (n->type) {
1.124 schwarze 727: case ROFFT_BLOCK:
1.69 schwarze 728: mt->fl &= ~MANT_LITERAL;
1.77 schwarze 729: mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
730: mt->offset = term_len(p, p->defindent);
1.111 schwarze 731:
732: /*
733: * No vertical space before the first section
734: * and after an empty section.
735: */
736:
737: do {
738: n = n->prev;
1.162 schwarze 739: } while (n != NULL && n->tok >= MAN_TH &&
1.143 schwarze 740: termacts[n->tok].flags & MAN_NOTEXT);
1.163 schwarze 741: if (n == NULL || n->type == ROFFT_COMMENT ||
742: (n->tok == MAN_SH && n->body->child == NULL))
1.29 schwarze 743: break;
1.111 schwarze 744:
1.88 schwarze 745: for (i = 0; i < mt->pardist; i++)
746: term_vspace(p);
1.11 schwarze 747: break;
1.124 schwarze 748: case ROFFT_HEAD:
1.21 schwarze 749: term_fontrepl(p, TERMFONT_BOLD);
1.156 schwarze 750: p->tcol->offset = 0;
751: p->tcol->rmargin = mt->offset;
1.129 schwarze 752: p->trailspace = mt->offset;
753: p->flags |= TERMP_NOBREAK | TERMP_BRIND;
1.11 schwarze 754: break;
1.124 schwarze 755: case ROFFT_BODY:
1.156 schwarze 756: p->tcol->offset = mt->offset;
757: p->tcol->rmargin = p->maxrmargin;
1.129 schwarze 758: p->trailspace = 0;
759: p->flags &= ~(TERMP_NOBREAK | TERMP_BRIND);
1.11 schwarze 760: break;
761: default:
762: break;
763: }
764:
1.138 schwarze 765: return 1;
1.1 kristaps 766: }
767:
768: static void
769: post_SH(DECL_ARGS)
770: {
1.100 schwarze 771:
1.11 schwarze 772: switch (n->type) {
1.124 schwarze 773: case ROFFT_HEAD:
1.11 schwarze 774: term_newln(p);
775: break;
1.124 schwarze 776: case ROFFT_BODY:
1.11 schwarze 777: term_newln(p);
778: break;
779: default:
1.13 schwarze 780: break;
781: }
782: }
783:
784: static int
785: pre_RS(DECL_ARGS)
786: {
1.116 schwarze 787: struct roffsu su;
1.13 schwarze 788:
789: switch (n->type) {
1.124 schwarze 790: case ROFFT_BLOCK:
1.13 schwarze 791: term_newln(p);
1.138 schwarze 792: return 1;
1.124 schwarze 793: case ROFFT_HEAD:
1.138 schwarze 794: return 0;
1.13 schwarze 795: default:
796: break;
797: }
798:
1.118 schwarze 799: n = n->parent->head;
800: n->aux = SHRT_MAX + 1;
1.130 schwarze 801: if (n->child == NULL)
802: n->aux = mt->lmargin[mt->lmargincur];
1.157 schwarze 803: else if (a2roffsu(n->child->string, &su, SCALE_EN) != NULL)
1.158 schwarze 804: n->aux = term_hen(p, &su);
1.118 schwarze 805: if (n->aux < 0 && (size_t)(-n->aux) > mt->offset)
806: n->aux = -mt->offset;
807: else if (n->aux > SHRT_MAX)
808: n->aux = term_len(p, p->defindent);
1.13 schwarze 809:
1.118 schwarze 810: mt->offset += n->aux;
1.156 schwarze 811: p->tcol->offset = mt->offset;
812: p->tcol->rmargin = p->maxrmargin;
1.13 schwarze 813:
1.70 schwarze 814: if (++mt->lmarginsz < MAXMARGINS)
815: mt->lmargincur = mt->lmarginsz;
816:
1.131 schwarze 817: mt->lmargin[mt->lmargincur] = term_len(p, p->defindent);
1.138 schwarze 818: return 1;
1.13 schwarze 819: }
820:
821: static void
822: post_RS(DECL_ARGS)
823: {
824:
825: switch (n->type) {
1.124 schwarze 826: case ROFFT_BLOCK:
1.69 schwarze 827: return;
1.124 schwarze 828: case ROFFT_HEAD:
1.69 schwarze 829: return;
1.13 schwarze 830: default:
831: term_newln(p);
1.11 schwarze 832: break;
833: }
1.69 schwarze 834:
1.118 schwarze 835: mt->offset -= n->parent->head->aux;
1.156 schwarze 836: p->tcol->offset = mt->offset;
1.70 schwarze 837:
838: if (--mt->lmarginsz < MAXMARGINS)
839: mt->lmargincur = mt->lmarginsz;
1.91 schwarze 840: }
841:
842: static int
843: pre_UR(DECL_ARGS)
844: {
845:
1.138 schwarze 846: return n->type != ROFFT_HEAD;
1.91 schwarze 847: }
848:
849: static void
850: post_UR(DECL_ARGS)
851: {
852:
1.124 schwarze 853: if (n->type != ROFFT_BLOCK)
1.91 schwarze 854: return;
855:
856: term_word(p, "<");
857: p->flags |= TERMP_NOSPACE;
858:
859: if (NULL != n->child->child)
860: print_man_node(p, mt, n->child->child, meta);
861:
862: p->flags |= TERMP_NOSPACE;
863: term_word(p, ">");
1.47 schwarze 864: }
865:
1.1 kristaps 866: static void
1.19 schwarze 867: print_man_node(DECL_ARGS)
1.1 kristaps 868: {
1.21 schwarze 869: int c;
1.1 kristaps 870:
871: switch (n->type) {
1.124 schwarze 872: case ROFFT_TEXT:
1.61 schwarze 873: /*
874: * If we have a blank line, output a vertical space.
875: * If we have a space as the first character, break
876: * before printing the line's data.
877: */
1.153 schwarze 878: if (*n->string == '\0') {
1.160 schwarze 879: if (p->flags & TERMP_NONEWLINE)
880: term_newln(p);
881: else
882: term_vspace(p);
1.61 schwarze 883: return;
1.153 schwarze 884: } else if (*n->string == ' ' && n->flags & NODE_LINE &&
885: (p->flags & TERMP_NONEWLINE) == 0)
1.60 schwarze 886: term_newln(p);
1.165 schwarze 887: else if (n->flags & NODE_DELIMC)
888: p->flags |= TERMP_NOSPACE;
1.21 schwarze 889:
1.1 kristaps 890: term_word(p, n->string);
1.84 schwarze 891: goto out;
1.163 schwarze 892: case ROFFT_COMMENT:
893: return;
1.124 schwarze 894: case ROFFT_EQN:
1.141 schwarze 895: if ( ! (n->flags & NODE_LINE))
1.105 schwarze 896: p->flags |= TERMP_NOSPACE;
1.71 schwarze 897: term_eqn(p, n->eqn);
1.141 schwarze 898: if (n->next != NULL && ! (n->next->flags & NODE_LINE))
1.106 schwarze 899: p->flags |= TERMP_NOSPACE;
1.61 schwarze 900: return;
1.124 schwarze 901: case ROFFT_TBL:
1.122 schwarze 902: if (p->tbl.cols == NULL)
903: term_vspace(p);
1.58 schwarze 904: term_tbl(p, n->span);
1.61 schwarze 905: return;
1.1 kristaps 906: default:
907: break;
908: }
909:
1.146 schwarze 910: if (n->tok < ROFF_MAX) {
1.147 schwarze 911: roff_term_pre(p, n);
1.146 schwarze 912: return;
913: }
914:
915: assert(n->tok >= MAN_TH && n->tok <= MAN_MAX);
1.61 schwarze 916: if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
917: term_fontrepl(p, TERMFONT_NONE);
918:
919: c = 1;
920: if (termacts[n->tok].pre)
1.89 schwarze 921: c = (*termacts[n->tok].pre)(p, mt, n, meta);
1.61 schwarze 922:
1.1 kristaps 923: if (c && n->child)
1.89 schwarze 924: print_man_nodelist(p, mt, n->child, meta);
1.1 kristaps 925:
1.61 schwarze 926: if (termacts[n->tok].post)
1.89 schwarze 927: (*termacts[n->tok].post)(p, mt, n, meta);
1.61 schwarze 928: if ( ! (MAN_NOTEXT & termacts[n->tok].flags))
929: term_fontrepl(p, TERMFONT_NONE);
1.30 schwarze 930:
1.84 schwarze 931: out:
932: /*
933: * If we're in a literal context, make sure that words
934: * together on the same line stay together. This is a
935: * POST-printing call, so we check the NEXT word. Since
936: * -man doesn't have nested macros, we don't need to be
937: * more specific than this.
938: */
1.110 schwarze 939: if (mt->fl & MANT_LITERAL &&
940: ! (p->flags & (TERMP_NOBREAK | TERMP_NONEWLINE)) &&
1.141 schwarze 941: (n->next == NULL || n->next->flags & NODE_LINE)) {
1.155 schwarze 942: p->flags |= TERMP_BRNEVER | TERMP_NOSPACE;
1.110 schwarze 943: if (n->string != NULL && *n->string != '\0')
1.84 schwarze 944: term_flushln(p);
945: else
946: term_newln(p);
1.155 schwarze 947: p->flags &= ~TERMP_BRNEVER;
1.156 schwarze 948: if (p->tcol->rmargin < p->maxrmargin &&
949: n->parent->tok == MAN_HP) {
950: p->tcol->offset = p->tcol->rmargin;
951: p->tcol->rmargin = p->maxrmargin;
1.155 schwarze 952: }
1.84 schwarze 953: }
1.141 schwarze 954: if (NODE_EOS & n->flags)
1.30 schwarze 955: p->flags |= TERMP_SENTENCE;
1.1 kristaps 956: }
957:
958:
959: static void
1.21 schwarze 960: print_man_nodelist(DECL_ARGS)
1.1 kristaps 961: {
1.11 schwarze 962:
1.121 schwarze 963: while (n != NULL) {
964: print_man_node(p, mt, n, meta);
965: n = n->next;
966: }
1.1 kristaps 967: }
968:
969: static void
1.126 schwarze 970: print_man_foot(struct termp *p, const struct roff_meta *meta)
1.1 kristaps 971: {
1.101 schwarze 972: char *title;
1.109 schwarze 973: size_t datelen, titlen;
1.41 schwarze 974:
1.80 schwarze 975: assert(meta->title);
976: assert(meta->msec);
977: assert(meta->date);
1.21 schwarze 978:
979: term_fontrepl(p, TERMFONT_NONE);
1.1 kristaps 980:
1.103 schwarze 981: if (meta->hasbody)
982: term_vspace(p);
1.81 schwarze 983:
984: /*
985: * Temporary, undocumented option to imitate mdoc(7) output.
1.126 schwarze 986: * In the bottom right corner, use the operating system
987: * instead of the title.
1.81 schwarze 988: */
989:
1.79 schwarze 990: if ( ! p->mdocstyle) {
1.103 schwarze 991: if (meta->hasbody) {
992: term_vspace(p);
993: term_vspace(p);
994: }
1.101 schwarze 995: mandoc_asprintf(&title, "%s(%s)",
996: meta->title, meta->msec);
1.126 schwarze 997: } else if (meta->os) {
998: title = mandoc_strdup(meta->os);
1.79 schwarze 999: } else {
1.101 schwarze 1000: title = mandoc_strdup("");
1.79 schwarze 1001: }
1.78 schwarze 1002: datelen = term_strlen(p, meta->date);
1.1 kristaps 1003:
1.126 schwarze 1004: /* Bottom left corner: operating system. */
1.81 schwarze 1005:
1.1 kristaps 1006: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
1.93 schwarze 1007: p->trailspace = 1;
1.156 schwarze 1008: p->tcol->offset = 0;
1009: p->tcol->rmargin = p->maxrmargin > datelen ?
1.109 schwarze 1010: (p->maxrmargin + term_len(p, 1) - datelen) / 2 : 0;
1.1 kristaps 1011:
1.126 schwarze 1012: if (meta->os)
1013: term_word(p, meta->os);
1.1 kristaps 1014: term_flushln(p);
1015:
1.81 schwarze 1016: /* At the bottom in the middle: manual date. */
1017:
1.156 schwarze 1018: p->tcol->offset = p->tcol->rmargin;
1.109 schwarze 1019: titlen = term_strlen(p, title);
1.156 schwarze 1020: p->tcol->rmargin = p->maxrmargin > titlen ?
1021: p->maxrmargin - titlen : 0;
1.72 schwarze 1022: p->flags |= TERMP_NOSPACE;
1.78 schwarze 1023:
1024: term_word(p, meta->date);
1025: term_flushln(p);
1026:
1.81 schwarze 1027: /* Bottom right corner: manual title and section. */
1028:
1.78 schwarze 1029: p->flags &= ~TERMP_NOBREAK;
1030: p->flags |= TERMP_NOSPACE;
1.93 schwarze 1031: p->trailspace = 0;
1.156 schwarze 1032: p->tcol->offset = p->tcol->rmargin;
1033: p->tcol->rmargin = p->maxrmargin;
1.1 kristaps 1034:
1.78 schwarze 1035: term_word(p, title);
1.1 kristaps 1036: term_flushln(p);
1.164 schwarze 1037:
1038: /*
1039: * Reset the terminal state for more output after the footer:
1040: * Some output modes, in particular PostScript and PDF, print
1041: * the header and the footer into a buffer such that it can be
1042: * reused for multiple output pages, then go on to format the
1043: * main text.
1044: */
1045:
1046: p->tcol->offset = 0;
1047: p->flags = 0;
1048:
1.101 schwarze 1049: free(title);
1.1 kristaps 1050: }
1051:
1052: static void
1.126 schwarze 1053: print_man_head(struct termp *p, const struct roff_meta *meta)
1.1 kristaps 1054: {
1.102 schwarze 1055: const char *volume;
1.101 schwarze 1056: char *title;
1.102 schwarze 1057: size_t vollen, titlen;
1.41 schwarze 1058:
1.89 schwarze 1059: assert(meta->title);
1060: assert(meta->msec);
1.1 kristaps 1061:
1.102 schwarze 1062: volume = NULL == meta->vol ? "" : meta->vol;
1063: vollen = term_strlen(p, volume);
1.1 kristaps 1064:
1.81 schwarze 1065: /* Top left corner: manual title and section. */
1066:
1.101 schwarze 1067: mandoc_asprintf(&title, "%s(%s)", meta->title, meta->msec);
1.42 schwarze 1068: titlen = term_strlen(p, title);
1.1 kristaps 1069:
1.73 schwarze 1070: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.93 schwarze 1071: p->trailspace = 1;
1.156 schwarze 1072: p->tcol->offset = 0;
1073: p->tcol->rmargin = 2 * (titlen+1) + vollen < p->maxrmargin ?
1.102 schwarze 1074: (p->maxrmargin - vollen + term_len(p, 1)) / 2 :
1.109 schwarze 1075: vollen < p->maxrmargin ? p->maxrmargin - vollen : 0;
1.1 kristaps 1076:
1077: term_word(p, title);
1078: term_flushln(p);
1079:
1.81 schwarze 1080: /* At the top in the middle: manual volume. */
1081:
1.72 schwarze 1082: p->flags |= TERMP_NOSPACE;
1.156 schwarze 1083: p->tcol->offset = p->tcol->rmargin;
1084: p->tcol->rmargin = p->tcol->offset + vollen + titlen <
1085: p->maxrmargin ? p->maxrmargin - titlen : p->maxrmargin;
1.1 kristaps 1086:
1.102 schwarze 1087: term_word(p, volume);
1.1 kristaps 1088: term_flushln(p);
1089:
1.81 schwarze 1090: /* Top right corner: title and section, again. */
1091:
1.1 kristaps 1092: p->flags &= ~TERMP_NOBREAK;
1.93 schwarze 1093: p->trailspace = 0;
1.156 schwarze 1094: if (p->tcol->rmargin + titlen <= p->maxrmargin) {
1.72 schwarze 1095: p->flags |= TERMP_NOSPACE;
1.156 schwarze 1096: p->tcol->offset = p->tcol->rmargin;
1097: p->tcol->rmargin = p->maxrmargin;
1.25 schwarze 1098: term_word(p, title);
1099: term_flushln(p);
1100: }
1.1 kristaps 1101:
1.73 schwarze 1102: p->flags &= ~TERMP_NOSPACE;
1.156 schwarze 1103: p->tcol->offset = 0;
1104: p->tcol->rmargin = p->maxrmargin;
1.29 schwarze 1105:
1.100 schwarze 1106: /*
1.81 schwarze 1107: * Groff prints three blank lines before the content.
1108: * Do the same, except in the temporary, undocumented
1109: * mode imitating mdoc(7) output.
1.29 schwarze 1110: */
1111:
1112: term_vspace(p);
1.79 schwarze 1113: if ( ! p->mdocstyle) {
1114: term_vspace(p);
1115: term_vspace(p);
1116: }
1.101 schwarze 1117: free(title);
1.1 kristaps 1118: }