[BACK]Return to mdocterm.c CVS log [TXT][DIR] Up to [cvsweb.bsd.lv] / mandoc

Annotation of mandoc/mdocterm.c, Revision 1.26

1.26    ! kristaps    1: /* $Id: mdocterm.c,v 1.25 2009/03/02 17:14:46 kristaps Exp $ */
1.1       kristaps    2: /*
                      3:  * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
                      4:  *
                      5:  * Permission to use, copy, modify, and distribute this software for any
                      6:  * purpose with or without fee is hereby granted, provided that the
                      7:  * above copyright notice and this permission notice appear in all
                      8:  * copies.
                      9:  *
                     10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
                     11:  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
                     12:  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
                     13:  * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
                     14:  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
                     15:  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
                     16:  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
                     17:  * PERFORMANCE OF THIS SOFTWARE.
                     18:  */
1.18      kristaps   19: #include <sys/utsname.h>
                     20:
1.1       kristaps   21: #include <assert.h>
1.3       kristaps   22: #include <ctype.h>
1.1       kristaps   23: #include <err.h>
                     24: #include <getopt.h>
1.3       kristaps   25: #include <stdio.h>
1.1       kristaps   26: #include <stdlib.h>
1.3       kristaps   27: #include <string.h>
1.7       kristaps   28: #ifndef __OpenBSD__
1.5       kristaps   29: #include <time.h>
                     30: #endif
                     31:
1.2       kristaps   32: #include "mmain.h"
1.1       kristaps   33: #include "term.h"
                     34:
1.3       kristaps   35: static void              body(struct termp *,
1.12      kristaps   36:                                struct termpair *,
1.3       kristaps   37:                                const struct mdoc_meta *,
                     38:                                const struct mdoc_node *);
                     39: static void              header(struct termp *,
                     40:                                const struct mdoc_meta *);
                     41: static void              footer(struct termp *,
                     42:                                const struct mdoc_meta *);
                     43:
                     44: static void              pword(struct termp *, const char *, size_t);
1.25      kristaps   45: static void              pescape(struct termp *, const char *,
                     46:                                size_t *, size_t);
                     47: static void              nescape(struct termp *,
1.12      kristaps   48:                                const char *, size_t);
1.3       kristaps   49: static void              chara(struct termp *, char);
1.25      kristaps   50: static void              stringa(struct termp *,
                     51:                                const char *, size_t);
                     52: static void              symbola(struct termp *, enum tsym);
1.26    ! kristaps   53: static void              stylea(struct termp *, enum tstyle);
1.3       kristaps   54:
                     55: #ifdef __linux__
                     56: extern size_t            strlcat(char *, const char *, size_t);
                     57: extern size_t            strlcpy(char *, const char *, size_t);
                     58: #endif
                     59:
1.25      kristaps   60: static struct termsym    termsym_ansi[] = {
                     61:        { "]", 1 },             /* TERMSYM_RBRACK */
                     62:        { "[", 1 },             /* TERMSYM_LBRACK */
                     63:        { "<-", 2 },            /* TERMSYM_LARROW */
                     64:        { "->", 2 },            /* TERMSYM_RARROW */
                     65:        { "^", 1 },             /* TERMSYM_UARROW */
                     66:        { "v", 1 },             /* TERMSYM_DARROW */
                     67:        { "`", 1 },             /* TERMSYM_LSQUOTE */
                     68:        { "\'", 1 },            /* TERMSYM_RSQUOTE */
                     69:        { "\'", 1 },            /* TERMSYM_SQUOTE */
                     70:        { "``", 2 },            /* TERMSYM_LDQUOTE */
                     71:        { "\'\'", 2 },          /* TERMSYM_RDQUOTE */
                     72:        { "\"", 1 },            /* TERMSYM_DQUOTE */
                     73:        { "<", 1 },             /* TERMSYM_LT */
                     74:        { ">", 1 },             /* TERMSYM_GT */
                     75:        { "<=", 2 },            /* TERMSYM_LE */
                     76:        { ">=", 2 },            /* TERMSYM_GE */
                     77:        { "==", 2 },            /* TERMSYM_EQ */
                     78:        { "!=", 2 },            /* TERMSYM_NEQ */
                     79:        { "\'", 1 },            /* TERMSYM_ACUTE */
                     80:        { "`", 1 },             /* TERMSYM_GRAVE */
                     81:        { "pi", 2 },            /* TERMSYM_PI */
                     82:        { "+=", 2 },            /* TERMSYM_PLUSMINUS */
                     83:        { "oo", 2 },            /* TERMSYM_INF */
                     84:        { "infinity", 8 },      /* TERMSYM_INF2 */
                     85:        { "NaN", 3 },           /* TERMSYM_NAN */
                     86:        { "|", 1 },             /* TERMSYM_BAR */
                     87:        { "o", 1 },             /* TERMSYM_BULLET */
                     88:        { "&", 1 },             /* TERMSYM_AND */
                     89:        { "|", 1 },             /* TERMSYM_OR */
                     90: };
                     91:
                     92: static const char        ansi_clear[]  = { 27, '[', '0', 'm' };
                     93: static const char        ansi_bold[]  = { 27, '[', '1', 'm' };
                     94: static const char        ansi_under[]  = { 27, '[', '4', 'm' };
                     95:
                     96: static struct termsym    termstyle_ansi[] = {
                     97:        { ansi_clear, 4 },
                     98:        { ansi_bold, 4 },
                     99:        { ansi_under, 4 }
                    100: };
                    101:
1.3       kristaps  102:
1.1       kristaps  103: int
                    104: main(int argc, char *argv[])
                    105: {
1.2       kristaps  106:        struct mmain    *p;
                    107:        const struct mdoc *mdoc;
1.3       kristaps  108:        struct termp     termp;
1.2       kristaps  109:
                    110:        p = mmain_alloc();
1.1       kristaps  111:
1.3       kristaps  112:        if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
1.2       kristaps  113:                mmain_exit(p, 1);
1.1       kristaps  114:
1.3       kristaps  115:        if (NULL == (mdoc = mmain_mdoc(p)))
                    116:                mmain_exit(p, 1);
                    117:
1.19      kristaps  118:        termp.maxrmargin = 78; /* XXX */
1.3       kristaps  119:        termp.rmargin = termp.maxrmargin;
                    120:        termp.maxcols = 1024;
                    121:        termp.offset = termp.col = 0;
                    122:        termp.flags = TERMP_NOSPACE;
1.25      kristaps  123:        termp.symtab = termsym_ansi;
                    124:        termp.styletab = termstyle_ansi;
1.3       kristaps  125:
                    126:        if (NULL == (termp.buf = malloc(termp.maxcols)))
                    127:                err(1, "malloc");
                    128:
1.15      kristaps  129:        header(&termp, mdoc_meta(mdoc));
1.12      kristaps  130:        body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3       kristaps  131:        footer(&termp, mdoc_meta(mdoc));
                    132:
                    133:        free(termp.buf);
                    134:
                    135:        mmain_exit(p, 0);
                    136:        /* NOTREACHED */
                    137: }
                    138:
                    139:
1.25      kristaps  140: /*
                    141:  * Flush a line of text.  A "line" is loosely defined as being something
                    142:  * that should be followed by a newline, regardless of whether it's
                    143:  * broken apart by newlines getting there.  A line can also be a
                    144:  * fragment of a columnar list.
                    145:  *
                    146:  * Specifically, a line is whatever's in p->buf of length p->col, which
                    147:  * is zeroed after this function returns.
                    148:  *
                    149:  * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
                    150:  * critical importance here.  Their behaviour follows:
                    151:  *
                    152:  *  - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
                    153:  *    offset value.  This is useful when doing columnar lists where the
                    154:  *    prior column has right-padded.
                    155:  *
                    156:  *  - TERMP_LITERAL: don't break apart words.  Note that a long literal
                    157:  *    word will violate the right margin.
                    158:  *
                    159:  *  - TERMP_NOBREAK: this is the most important and is used when making
                    160:  *    columns.  In short: don't print a newline and instead pad to the
                    161:  *    right margin.  Used in conjunction with TERMP_NOLPAD.
                    162:  *
                    163:  *  In-line line breaking:
                    164:  *
                    165:  *  If TERMP_NOBREAK is specified and the line overruns the right
                    166:  *  margin, it will break and pad-right to the right margin after
                    167:  *  writing.  If maxrmargin is violated, it will break and continue
                    168:  *  writing from the right-margin, which will lead to the above
                    169:  *  scenario upon exit.
                    170:  *
                    171:  *  Otherwise, the line will break at the right margin.  Extremely long
                    172:  *  lines will cause the system to emit a warning (TODO: hyphenate, if
                    173:  *  possible).
                    174:  */
1.3       kristaps  175: void
                    176: flushln(struct termp *p)
                    177: {
1.25      kristaps  178:        size_t           i, j, vsz, vis, maxvis, mmax, bp;
1.3       kristaps  179:
                    180:        /*
                    181:         * First, establish the maximum columns of "visible" content.
                    182:         * This is usually the difference between the right-margin and
                    183:         * an indentation, but can be, for tagged lists or columns, a
                    184:         * small set of values.
                    185:         */
                    186:
                    187:        assert(p->offset < p->rmargin);
                    188:        maxvis = p->rmargin - p->offset;
1.25      kristaps  189:        mmax = p->maxrmargin - p->offset;
                    190:        bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.3       kristaps  191:        vis = 0;
                    192:
                    193:        /*
                    194:         * If in the standard case (left-justified), then begin with our
                    195:         * indentation, otherwise (columns, etc.) just start spitting
                    196:         * out text.
                    197:         */
                    198:
                    199:        if ( ! (p->flags & TERMP_NOLPAD))
                    200:                /* LINTED */
                    201:                for (j = 0; j < p->offset; j++)
                    202:                        putchar(' ');
                    203:
                    204:        for (i = 0; i < p->col; i++) {
                    205:                /*
                    206:                 * Count up visible word characters.  Control sequences
1.23      kristaps  207:                 * (starting with the CSI) aren't counted.  A space
                    208:                 * generates a non-printing word, which is valid (the
                    209:                 * space is printed according to regular spacing rules).
1.3       kristaps  210:                 */
                    211:
1.25      kristaps  212:                /* FIXME: make non-ANSI friendly. */
                    213:
1.3       kristaps  214:                /* LINTED */
                    215:                for (j = i, vsz = 0; j < p->col; j++) {
1.25      kristaps  216:                        if (isspace((int)p->buf[j]))
1.3       kristaps  217:                                break;
                    218:                        else if (27 == p->buf[j]) {
                    219:                                assert(j + 4 <= p->col);
                    220:                                j += 3;
                    221:                        } else
                    222:                                vsz++;
                    223:                }
                    224:
                    225:                /*
1.25      kristaps  226:                 * Do line-breaking.  If we're greater than our
                    227:                 * break-point and already in-line, break to the next
                    228:                 * line and start writing.  If we're at the line start,
                    229:                 * then write out the word (TODO: hyphenate) and break
                    230:                 * in a subsequent loop invocation.
1.3       kristaps  231:                 */
                    232:
1.22      kristaps  233:                if ( ! (TERMP_NOBREAK & p->flags)) {
1.25      kristaps  234:                        if (vis && vis + vsz > bp) {
1.22      kristaps  235:                                putchar('\n');
                    236:                                for (j = 0; j < p->offset; j++)
                    237:                                        putchar(' ');
                    238:                                vis = 0;
1.25      kristaps  239:                        } else if (vis + vsz > bp)
                    240:                                warnx("word breaks right margin");
                    241:
                    242:                        /* TODO: hyphenate. */
                    243:
                    244:                } else {
                    245:                        if (vis && vis + vsz > bp) {
                    246:                                putchar('\n');
                    247:                                for (j = 0; j < p->rmargin; j++)
                    248:                                        putchar(' ');
                    249:                                vis = p->rmargin;
                    250:                        } else if (vis + vsz > bp)
                    251:                                warnx("word breaks right margin");
                    252:
                    253:                        /* TODO: hyphenate. */
1.24      kristaps  254:                }
1.3       kristaps  255:
                    256:                /*
                    257:                 * Write out the word and a trailing space.  Omit the
1.25      kristaps  258:                 * space if we're the last word in the line or beyond
                    259:                 * our breakpoint.
1.3       kristaps  260:                 */
                    261:
                    262:                for ( ; i < p->col; i++) {
1.25      kristaps  263:                        if (isspace((int)p->buf[i]))
1.3       kristaps  264:                                break;
                    265:                        putchar(p->buf[i]);
                    266:                }
                    267:                vis += vsz;
1.25      kristaps  268:                if (i < p->col && vis <= bp) {
1.3       kristaps  269:                        putchar(' ');
                    270:                        vis++;
                    271:                }
                    272:        }
                    273:
1.25      kristaps  274:        /*
                    275:         * If we've overstepped our maximum visible no-break space, then
                    276:         * cause a newline and offset at the right margin.
                    277:         */
                    278:
1.22      kristaps  279:        if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
                    280:                putchar('\n');
                    281:                for (i = 0; i < p->rmargin; i++)
                    282:                        putchar(' ');
                    283:                p->col = 0;
                    284:                return;
                    285:        }
                    286:
1.3       kristaps  287:        /*
                    288:         * If we're not to right-marginalise it (newline), then instead
                    289:         * pad to the right margin and stay off.
                    290:         */
                    291:
                    292:        if (p->flags & TERMP_NOBREAK) {
1.22      kristaps  293:                for ( ; vis < maxvis; vis++)
                    294:                        putchar(' ');
1.3       kristaps  295:        } else
                    296:                putchar('\n');
                    297:
                    298:        p->col = 0;
                    299: }
                    300:
                    301:
1.25      kristaps  302: /*
                    303:  * A newline only breaks an existing line; it won't assert vertical
                    304:  * space.  All data in the output buffer is flushed prior to the newline
                    305:  * assertion.
                    306:  */
1.3       kristaps  307: void
                    308: newln(struct termp *p)
                    309: {
1.1       kristaps  310:
1.3       kristaps  311:        p->flags |= TERMP_NOSPACE;
1.12      kristaps  312:        if (0 == p->col) {
                    313:                p->flags &= ~TERMP_NOLPAD;
1.3       kristaps  314:                return;
1.12      kristaps  315:        }
1.3       kristaps  316:        flushln(p);
1.11      kristaps  317:        p->flags &= ~TERMP_NOLPAD;
1.3       kristaps  318: }
                    319:
                    320:
1.25      kristaps  321: /*
                    322:  * Asserts a vertical space (a full, empty line-break between lines).
                    323:  * Note that if used twice, this will cause two blank spaces and so on.
                    324:  * All data in the output buffer is flushed prior to the newline
                    325:  * assertion.
                    326:  */
1.3       kristaps  327: void
                    328: vspace(struct termp *p)
                    329: {
                    330:
                    331:        newln(p);
                    332:        putchar('\n');
                    333: }
                    334:
                    335:
1.25      kristaps  336: /*
                    337:  * Break apart a word into "pwords" (partial-words, usually from
                    338:  * breaking up a phrase into individual words) and, eventually, put them
                    339:  * into the output buffer.  If we're a literal word, then don't break up
                    340:  * the word and put it verbatim into the output buffer.
                    341:  */
1.3       kristaps  342: void
                    343: word(struct termp *p, const char *word)
                    344: {
                    345:        size_t           i, j, len;
                    346:
                    347:        if (p->flags & TERMP_LITERAL) {
                    348:                pword(p, word, strlen(word));
                    349:                return;
                    350:        }
                    351:
                    352:        len = strlen(word);
                    353:        assert(len > 0);
                    354:
                    355:        if (mdoc_isdelim(word)) {
                    356:                if ( ! (p->flags & TERMP_IGNDELIM))
                    357:                        p->flags |= TERMP_NOSPACE;
                    358:                p->flags &= ~TERMP_IGNDELIM;
                    359:        }
                    360:
                    361:        /* LINTED */
                    362:        for (j = i = 0; i < len; i++) {
1.25      kristaps  363:                if ( ! isspace((int)word[i])) {
1.3       kristaps  364:                        j++;
                    365:                        continue;
1.20      kristaps  366:                }
                    367:
                    368:                /* Escaped spaces don't delimit... */
1.25      kristaps  369:                if (i > 0 && isspace((int)word[i]) &&
                    370:                                '\\' == word[i - 1]) {
1.20      kristaps  371:                        j++;
                    372:                        continue;
1.1       kristaps  373:                }
1.20      kristaps  374:
1.3       kristaps  375:                if (0 == j)
                    376:                        continue;
                    377:                assert(i >= j);
                    378:                pword(p, &word[i - j], j);
                    379:                j = 0;
                    380:        }
                    381:        if (j > 0) {
                    382:                assert(i >= j);
                    383:                pword(p, &word[i - j], j);
                    384:        }
                    385: }
                    386:
                    387:
1.25      kristaps  388: /*
                    389:  * This is the main function for printing out nodes.  It's constituted
                    390:  * of PRE and POST functions, which correspond to prefix and infix
                    391:  * processing.  The termpair structure allows data to persist between
                    392:  * prefix and postfix invocations.
                    393:  */
1.3       kristaps  394: static void
1.12      kristaps  395: body(struct termp *p, struct termpair *ppair,
                    396:                const struct mdoc_meta *meta,
1.3       kristaps  397:                const struct mdoc_node *node)
                    398: {
                    399:        int              dochild;
1.9       kristaps  400:        struct termpair  pair;
1.3       kristaps  401:
                    402:        /* Pre-processing. */
                    403:
                    404:        dochild = 1;
1.12      kristaps  405:        pair.ppair = ppair;
1.9       kristaps  406:        pair.type = 0;
1.11      kristaps  407:        pair.offset = pair.rmargin = 0;
1.10      kristaps  408:        pair.flag = 0;
1.12      kristaps  409:        pair.count = 0;
1.3       kristaps  410:
                    411:        if (MDOC_TEXT != node->type) {
                    412:                if (termacts[node->tok].pre)
1.9       kristaps  413:                        if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3       kristaps  414:                                dochild = 0;
                    415:        } else /* MDOC_TEXT == node->type */
                    416:                word(p, node->data.text.string);
                    417:
                    418:        /* Children. */
                    419:
1.10      kristaps  420:        if (TERMPAIR_FLAG & pair.type)
                    421:                p->flags |= pair.flag;
1.9       kristaps  422:
1.3       kristaps  423:        if (dochild && node->child)
1.12      kristaps  424:                body(p, &pair, meta, node->child);
1.3       kristaps  425:
1.10      kristaps  426:        if (TERMPAIR_FLAG & pair.type)
                    427:                p->flags &= ~pair.flag;
1.9       kristaps  428:
1.3       kristaps  429:        /* Post-processing. */
                    430:
                    431:        if (MDOC_TEXT != node->type)
                    432:                if (termacts[node->tok].post)
1.9       kristaps  433:                        (*termacts[node->tok].post)(p, &pair, meta, node);
1.3       kristaps  434:
                    435:        /* Siblings. */
1.1       kristaps  436:
1.3       kristaps  437:        if (node->next)
1.12      kristaps  438:                body(p, ppair, meta, node->next);
1.3       kristaps  439: }
                    440:
                    441:
                    442: static void
                    443: footer(struct termp *p, const struct mdoc_meta *meta)
                    444: {
                    445:        struct tm       *tm;
                    446:        char            *buf, *os;
                    447:
                    448:        if (NULL == (buf = malloc(p->rmargin)))
                    449:                err(1, "malloc");
                    450:        if (NULL == (os = malloc(p->rmargin)))
                    451:                err(1, "malloc");
                    452:
                    453:        tm = localtime(&meta->date);
                    454:
1.7       kristaps  455: #ifdef __OpenBSD__
                    456:        if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
                    457: #else
1.3       kristaps  458:        if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
                    459: #endif
                    460:                err(1, "strftime");
                    461:
1.15      kristaps  462:        (void)strlcpy(os, meta->os, p->rmargin);
1.3       kristaps  463:
1.16      kristaps  464:        /*
                    465:         * This is /slightly/ different from regular groff output
                    466:         * because we don't have page numbers.  Print the following:
                    467:         *
                    468:         * OS                                            MDOCDATE
                    469:         */
                    470:
1.15      kristaps  471:        vspace(p);
1.3       kristaps  472:
1.15      kristaps  473:        p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
                    474:        p->rmargin = p->maxrmargin - strlen(buf);
                    475:        p->offset = 0;
1.3       kristaps  476:
1.15      kristaps  477:        word(p, os);
                    478:        flushln(p);
1.3       kristaps  479:
1.15      kristaps  480:        p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
                    481:        p->offset = p->rmargin;
                    482:        p->rmargin = p->maxrmargin;
                    483:        p->flags &= ~TERMP_NOBREAK;
                    484:
                    485:        word(p, buf);
                    486:        flushln(p);
1.1       kristaps  487:
1.3       kristaps  488:        free(buf);
                    489:        free(os);
1.1       kristaps  490: }
                    491:
                    492:
1.3       kristaps  493: static void
                    494: header(struct termp *p, const struct mdoc_meta *meta)
                    495: {
1.18      kristaps  496:        char            *buf, *title, *bufp, *vbuf;
1.13      kristaps  497:        const char      *pp;
1.18      kristaps  498:        struct utsname   uts;
                    499:
                    500:        p->rmargin = p->maxrmargin;
                    501:        p->offset = 0;
1.3       kristaps  502:
                    503:        if (NULL == (buf = malloc(p->rmargin)))
                    504:                err(1, "malloc");
                    505:        if (NULL == (title = malloc(p->rmargin)))
                    506:                err(1, "malloc");
1.18      kristaps  507:        if (NULL == (vbuf = malloc(p->rmargin)))
                    508:                err(1, "malloc");
1.3       kristaps  509:
1.18      kristaps  510:        if (NULL == (pp = mdoc_vol2a(meta->vol))) {
1.3       kristaps  511:                switch (meta->msec) {
                    512:                case (MSEC_1):
                    513:                        /* FALLTHROUGH */
                    514:                case (MSEC_6):
                    515:                        /* FALLTHROUGH */
                    516:                case (MSEC_7):
                    517:                        pp = mdoc_vol2a(VOL_URM);
                    518:                        break;
                    519:                case (MSEC_8):
                    520:                        pp = mdoc_vol2a(VOL_SMM);
                    521:                        break;
                    522:                case (MSEC_2):
                    523:                        /* FALLTHROUGH */
                    524:                case (MSEC_3):
                    525:                        /* FALLTHROUGH */
                    526:                case (MSEC_4):
                    527:                        /* FALLTHROUGH */
                    528:                case (MSEC_5):
                    529:                        pp = mdoc_vol2a(VOL_PRM);
                    530:                        break;
                    531:                case (MSEC_9):
                    532:                        pp = mdoc_vol2a(VOL_KM);
                    533:                        break;
                    534:                default:
                    535:                        break;
                    536:                }
1.18      kristaps  537:        }
                    538:        vbuf[0] = 0;
                    539:
                    540:        if (pp) {
                    541:                if (-1 == uname(&uts))
                    542:                        err(1, "uname");
                    543:                (void)strlcat(vbuf, uts.sysname, p->rmargin);
                    544:                (void)strlcat(vbuf, " ", p->rmargin);
                    545:        } else if (NULL == (pp = mdoc_msec2a(meta->msec)))
                    546:                pp = mdoc_msec2a(MSEC_local);
                    547:
                    548:        (void)strlcat(vbuf, pp, p->rmargin);
1.3       kristaps  549:
1.16      kristaps  550:        /*
                    551:         * The header is strange.  It has three components, which are
                    552:         * really two with the first duplicated.  It goes like this:
                    553:         *
                    554:         * IDENTIFIER              TITLE                   IDENTIFIER
                    555:         *
                    556:         * The IDENTIFIER is NAME(SECTION), which is the command-name
                    557:         * (if given, or "unknown" if not) followed by the manual page
                    558:         * section.  These are given in `Dt'.  The TITLE is a free-form
                    559:         * string depending on the manual volume.  If not specified, it
                    560:         * switches on the manual section.
                    561:         */
                    562:
1.13      kristaps  563:        if (mdoc_arch2a(meta->arch))
1.16      kristaps  564:                (void)snprintf(buf, p->rmargin, "%s (%s)",
1.18      kristaps  565:                                vbuf, mdoc_arch2a(meta->arch));
1.13      kristaps  566:        else
1.18      kristaps  567:                (void)strlcpy(buf, vbuf, p->rmargin);
1.13      kristaps  568:
                    569:        pp = mdoc_msec2a(meta->msec);
                    570:
                    571:        (void)snprintf(title, p->rmargin, "%s(%s)",
                    572:                        meta->title, pp ? pp : "");
                    573:
1.16      kristaps  574:        for (bufp = title; *bufp; bufp++)
                    575:                *bufp = toupper(*bufp);
                    576:
1.13      kristaps  577:        p->offset = 0;
                    578:        p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15      kristaps  579:        p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3       kristaps  580:
1.13      kristaps  581:        word(p, title);
                    582:        flushln(p);
1.3       kristaps  583:
1.15      kristaps  584:        p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13      kristaps  585:        p->offset = p->rmargin;
1.15      kristaps  586:        p->rmargin = p->maxrmargin - strlen(title);
1.3       kristaps  587:
1.13      kristaps  588:        word(p, buf);
                    589:        flushln(p);
1.3       kristaps  590:
1.13      kristaps  591:        p->offset = p->rmargin;
                    592:        p->rmargin = p->maxrmargin;
                    593:        p->flags &= ~TERMP_NOBREAK;
1.15      kristaps  594:        p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3       kristaps  595:
1.13      kristaps  596:        word(p, title);
                    597:        flushln(p);
1.3       kristaps  598:
1.13      kristaps  599:        p->rmargin = p->maxrmargin;
                    600:        p->offset = 0;
                    601:        p->flags &= ~TERMP_NOSPACE;
1.3       kristaps  602:
                    603:        free(title);
1.18      kristaps  604:        free(vbuf);
1.3       kristaps  605:        free(buf);
                    606: }
1.25      kristaps  607:
                    608:
                    609: /*
                    610:  * Determine the symbol indicated by an escape sequences, that is, one
                    611:  * starting with a backslash.  Once done, we pass this value into the
                    612:  * output buffer by way of the symbol table.
                    613:  */
                    614: static void
                    615: nescape(struct termp *p, const char *word, size_t len)
                    616: {
                    617:
                    618:        switch (len) {
                    619:        case (1):
                    620:                switch (word[0]) {
                    621:                case ('\\'):
                    622:                        /* FALLTHROUGH */
                    623:                case ('\''):
                    624:                        /* FALLTHROUGH */
                    625:                case ('`'):
                    626:                        /* FALLTHROUGH */
                    627:                case ('-'):
                    628:                        /* FALLTHROUGH */
                    629:                case (' '):
                    630:                        /* FALLTHROUGH */
                    631:                case ('.'):
                    632:                        chara(p, word[0]); /* FIXME */
                    633:                        break;
                    634:                case ('&'):
                    635:                        break;
                    636:                case ('e'):
                    637:                        chara(p, '\\'); /* FIXME */
                    638:                        break;
                    639:                case ('q'):
                    640:                        symbola(p, TERMSYM_DQUOTE);
                    641:                        break;
                    642:                default:
                    643:                        warnx("escape sequence not supported: %c",
                    644:                                        word[0]);
                    645:                        break;
                    646:                }
                    647:                break;
                    648:
                    649:        case (2):
                    650:                if ('r' == word[0] && 'B' == word[1])
                    651:                        symbola(p, TERMSYM_RBRACK);
                    652:                else if ('l' == word[0] && 'B' == word[1])
                    653:                        symbola(p, TERMSYM_LBRACK);
                    654:                else if ('l' == word[0] && 'q' == word[1])
                    655:                        symbola(p, TERMSYM_LDQUOTE);
                    656:                else if ('r' == word[0] && 'q' == word[1])
                    657:                        symbola(p, TERMSYM_RDQUOTE);
                    658:                else if ('o' == word[0] && 'q' == word[1])
                    659:                        symbola(p, TERMSYM_LSQUOTE);
                    660:                else if ('a' == word[0] && 'q' == word[1])
                    661:                        symbola(p, TERMSYM_RSQUOTE);
                    662:                else if ('<' == word[0] && '-' == word[1])
                    663:                        symbola(p, TERMSYM_LARROW);
                    664:                else if ('-' == word[0] && '>' == word[1])
                    665:                        symbola(p, TERMSYM_RARROW);
                    666:                else if ('b' == word[0] && 'u' == word[1])
                    667:                        symbola(p, TERMSYM_BULLET);
                    668:                else if ('<' == word[0] && '=' == word[1])
                    669:                        symbola(p, TERMSYM_LE);
                    670:                else if ('>' == word[0] && '=' == word[1])
                    671:                        symbola(p, TERMSYM_GE);
                    672:                else if ('=' == word[0] && '=' == word[1])
                    673:                        symbola(p, TERMSYM_EQ);
                    674:                else if ('+' == word[0] && '-' == word[1])
                    675:                        symbola(p, TERMSYM_PLUSMINUS);
                    676:                else if ('u' == word[0] && 'a' == word[1])
                    677:                        symbola(p, TERMSYM_UARROW);
                    678:                else if ('d' == word[0] && 'a' == word[1])
                    679:                        symbola(p, TERMSYM_DARROW);
                    680:                else if ('a' == word[0] && 'a' == word[1])
                    681:                        symbola(p, TERMSYM_ACUTE);
                    682:                else if ('g' == word[0] && 'a' == word[1])
                    683:                        symbola(p, TERMSYM_GRAVE);
                    684:                else if ('!' == word[0] && '=' == word[1])
                    685:                        symbola(p, TERMSYM_NEQ);
                    686:                else if ('i' == word[0] && 'f' == word[1])
                    687:                        symbola(p, TERMSYM_INF);
                    688:                else if ('n' == word[0] && 'a' == word[1])
                    689:                        symbola(p, TERMSYM_NAN);
                    690:                else if ('b' == word[0] && 'a' == word[1])
                    691:                        symbola(p, TERMSYM_BAR);
                    692:
                    693:                /* Deprecated forms. */
                    694:                else if ('A' == word[0] && 'm' == word[1])
                    695:                        symbola(p, TERMSYM_AMP);
                    696:                else if ('B' == word[0] && 'a' == word[1])
                    697:                        symbola(p, TERMSYM_BAR);
                    698:                else if ('I' == word[0] && 'f' == word[1])
                    699:                        symbola(p, TERMSYM_INF2);
                    700:                else if ('G' == word[0] && 'e' == word[1])
                    701:                        symbola(p, TERMSYM_GE);
                    702:                else if ('G' == word[0] && 't' == word[1])
                    703:                        symbola(p, TERMSYM_GT);
                    704:                else if ('L' == word[0] && 'e' == word[1])
                    705:                        symbola(p, TERMSYM_LE);
                    706:                else if ('L' == word[0] && 'q' == word[1])
                    707:                        symbola(p, TERMSYM_LDQUOTE);
                    708:                else if ('L' == word[0] && 't' == word[1])
                    709:                        symbola(p, TERMSYM_LT);
                    710:                else if ('N' == word[0] && 'a' == word[1])
                    711:                        symbola(p, TERMSYM_NAN);
                    712:                else if ('N' == word[0] && 'e' == word[1])
                    713:                        symbola(p, TERMSYM_NEQ);
                    714:                else if ('P' == word[0] && 'i' == word[1])
                    715:                        symbola(p, TERMSYM_PI);
                    716:                else if ('P' == word[0] && 'm' == word[1])
                    717:                        symbola(p, TERMSYM_PLUSMINUS);
                    718:                else if ('R' == word[0] && 'q' == word[1])
                    719:                        symbola(p, TERMSYM_RDQUOTE);
                    720:                else
                    721:                        warnx("escape sequence not supported: %c%c",
                    722:                                        word[0], word[1]);
                    723:                break;
                    724:
                    725:        default:
                    726:                warnx("escape sequence not supported");
                    727:                break;
                    728:        }
                    729: }
                    730:
                    731:
                    732: /*
                    733:  * Handle an escape sequence: determine its length and pass it to the
                    734:  * escape-symbol look table.  Note that we assume mdoc(3) has validated
                    735:  * the escape sequence (we assert upon badly-formed escape sequences).
                    736:  */
                    737: static void
                    738: pescape(struct termp *p, const char *word, size_t *i, size_t len)
                    739: {
                    740:        size_t           j;
                    741:
                    742:        (*i)++;
                    743:        assert(*i < len);
                    744:
                    745:        if ('(' == word[*i]) {
                    746:                (*i)++;
                    747:                assert(*i + 1 < len);
                    748:                nescape(p, &word[*i], 2);
                    749:                (*i)++;
                    750:                return;
                    751:
                    752:        } else if ('*' == word[*i]) {
                    753:                /* XXX - deprecated! */
                    754:                (*i)++;
                    755:                assert(*i < len);
                    756:                switch (word[*i]) {
                    757:                case ('('):
                    758:                        (*i)++;
                    759:                        assert(*i + 1 < len);
                    760:                        nescape(p, &word[*i], 2);
                    761:                        (*i)++;
                    762:                        return;
                    763:                case ('['):
                    764:                        break;
                    765:                default:
                    766:                        nescape(p, &word[*i], 1);
                    767:                        return;
                    768:                }
                    769:
                    770:        } else if ('[' != word[*i]) {
                    771:                nescape(p, &word[*i], 1);
                    772:                return;
                    773:        }
                    774:
                    775:        (*i)++;
                    776:        for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
                    777:                /* Loop... */ ;
                    778:
                    779:        assert(word[*i]);
                    780:        nescape(p, &word[*i - j], j);
                    781: }
                    782:
                    783:
                    784: /*
                    785:  * Handle pwords, partial words, which may be either a single word or a
                    786:  * phrase that cannot be broken down (such as a literal string).  This
                    787:  * handles word styling.
                    788:  */
                    789: static void
                    790: pword(struct termp *p, const char *word, size_t len)
                    791: {
                    792:        size_t           i;
                    793:
                    794:        if ( ! (TERMP_NOSPACE & p->flags) &&
                    795:                        ! (TERMP_LITERAL & p->flags))
                    796:                chara(p, ' ');
                    797:
                    798:        if ( ! (p->flags & TERMP_NONOSPACE))
                    799:                p->flags &= ~TERMP_NOSPACE;
                    800:
                    801:        /*
                    802:         * XXX - if literal and underlining, this will underline the
                    803:         * spaces between literal words.
                    804:         */
                    805:
                    806:        if (p->flags & TERMP_BOLD)
1.26    ! kristaps  807:                stylea(p, TERMSTYLE_BOLD);
1.25      kristaps  808:        if (p->flags & TERMP_UNDERLINE)
1.26    ! kristaps  809:                stylea(p, TERMSTYLE_UNDER);
1.25      kristaps  810:
                    811:        for (i = 0; i < len; i++) {
                    812:                if ('\\' == word[i]) {
                    813:                        pescape(p, word, &i, len);
                    814:                        continue;
                    815:                }
                    816:                chara(p, word[i]);
                    817:        }
                    818:
                    819:        if (p->flags & TERMP_BOLD ||
                    820:                        p->flags & TERMP_UNDERLINE)
1.26    ! kristaps  821:                stylea(p, TERMSTYLE_CLEAR);
1.25      kristaps  822: }
                    823:
                    824:
                    825: /*
                    826:  * Add a symbol to the output line buffer.
                    827:  */
                    828: static void
                    829: symbola(struct termp *p, enum tsym sym)
                    830: {
                    831:
                    832:        assert(p->symtab[sym].sym);
                    833:        stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
                    834: }
                    835:
                    836:
                    837: /*
1.26    ! kristaps  838:  * Add a style to the output line buffer.
        !           839:  */
        !           840: static void
        !           841: stylea(struct termp *p, enum tstyle style)
        !           842: {
        !           843:
        !           844:        assert(p->styletab[style].sym);
        !           845:        stringa(p, p->styletab[style].sym, p->styletab[style].sz);
        !           846: }
        !           847:
        !           848:
        !           849: /*
1.25      kristaps  850:  * Like chara() but for arbitrary-length buffers.  Resize the buffer by
                    851:  * a factor of two (if the buffer is less than that) or the buffer's
                    852:  * size.
                    853:  */
                    854: static void
                    855: stringa(struct termp *p, const char *c, size_t sz)
                    856: {
                    857:        size_t           s;
                    858:
                    859:        s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
                    860:
                    861:        assert(c);
                    862:        if (p->col + sz >= p->maxcols) {
                    863:                p->buf = realloc(p->buf, s);
                    864:                if (NULL == p->buf)
                    865:                        err(1, "realloc");
                    866:                p->maxcols = s;
                    867:        }
                    868:
                    869:        (void)memcpy(&p->buf[p->col], c, sz);
                    870:        p->col += sz;
                    871: }
                    872:
                    873:
                    874: /*
                    875:  * Insert a single character into the line-buffer.  If the buffer's
                    876:  * space is exceeded, then allocate more space by doubling the buffer
                    877:  * size.
                    878:  */
                    879: static void
                    880: chara(struct termp *p, char c)
                    881: {
                    882:
                    883:        if (p->col + 1 >= p->maxcols) {
                    884:                p->buf = realloc(p->buf, p->maxcols * 2);
                    885:                if (NULL == p->buf)
                    886:                        err(1, "malloc");
                    887:                p->maxcols *= 2;
                    888:        }
                    889:        p->buf[(p->col)++] = c;
                    890: }

CVSweb