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

Annotation of mandoc/mdocterm.c, Revision 1.25

1.25    ! kristaps    1: /* $Id: mdocterm.c,v 1.24 2009/03/02 12:09:32 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              style(struct termp *, enum tstyle);
        !            48: static void              nescape(struct termp *,
1.12      kristaps   49:                                const char *, size_t);
1.3       kristaps   50: static void              chara(struct termp *, char);
1.25    ! kristaps   51: static void              stringa(struct termp *,
        !            52:                                const char *, size_t);
        !            53: static void              symbola(struct termp *, enum tsym);
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:  * Apply a style to the output buffer.  This is looked up by means of
        !           734:  * the styletab.
        !           735:  */
        !           736: static void
        !           737: style(struct termp *p, enum tstyle esc)
        !           738: {
        !           739:
        !           740:        if (p->col + 4 >= p->maxcols)
        !           741:                errx(1, "line overrun");
        !           742:
        !           743:        p->buf[(p->col)++] = 27;
        !           744:        p->buf[(p->col)++] = '[';
        !           745:        switch (esc) {
        !           746:        case (TERMSTYLE_CLEAR):
        !           747:                p->buf[(p->col)++] = '0';
        !           748:                break;
        !           749:        case (TERMSTYLE_BOLD):
        !           750:                p->buf[(p->col)++] = '1';
        !           751:                break;
        !           752:        case (TERMSTYLE_UNDER):
        !           753:                p->buf[(p->col)++] = '4';
        !           754:                break;
        !           755:        default:
        !           756:                abort();
        !           757:                /* NOTREACHED */
        !           758:        }
        !           759:        p->buf[(p->col)++] = 'm';
        !           760: }
        !           761:
        !           762:
        !           763: /*
        !           764:  * Handle an escape sequence: determine its length and pass it to the
        !           765:  * escape-symbol look table.  Note that we assume mdoc(3) has validated
        !           766:  * the escape sequence (we assert upon badly-formed escape sequences).
        !           767:  */
        !           768: static void
        !           769: pescape(struct termp *p, const char *word, size_t *i, size_t len)
        !           770: {
        !           771:        size_t           j;
        !           772:
        !           773:        (*i)++;
        !           774:        assert(*i < len);
        !           775:
        !           776:        if ('(' == word[*i]) {
        !           777:                (*i)++;
        !           778:                assert(*i + 1 < len);
        !           779:                nescape(p, &word[*i], 2);
        !           780:                (*i)++;
        !           781:                return;
        !           782:
        !           783:        } else if ('*' == word[*i]) {
        !           784:                /* XXX - deprecated! */
        !           785:                (*i)++;
        !           786:                assert(*i < len);
        !           787:                switch (word[*i]) {
        !           788:                case ('('):
        !           789:                        (*i)++;
        !           790:                        assert(*i + 1 < len);
        !           791:                        nescape(p, &word[*i], 2);
        !           792:                        (*i)++;
        !           793:                        return;
        !           794:                case ('['):
        !           795:                        break;
        !           796:                default:
        !           797:                        nescape(p, &word[*i], 1);
        !           798:                        return;
        !           799:                }
        !           800:
        !           801:        } else if ('[' != word[*i]) {
        !           802:                nescape(p, &word[*i], 1);
        !           803:                return;
        !           804:        }
        !           805:
        !           806:        (*i)++;
        !           807:        for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
        !           808:                /* Loop... */ ;
        !           809:
        !           810:        assert(word[*i]);
        !           811:        nescape(p, &word[*i - j], j);
        !           812: }
        !           813:
        !           814:
        !           815: /*
        !           816:  * Handle pwords, partial words, which may be either a single word or a
        !           817:  * phrase that cannot be broken down (such as a literal string).  This
        !           818:  * handles word styling.
        !           819:  */
        !           820: static void
        !           821: pword(struct termp *p, const char *word, size_t len)
        !           822: {
        !           823:        size_t           i;
        !           824:
        !           825:        if ( ! (TERMP_NOSPACE & p->flags) &&
        !           826:                        ! (TERMP_LITERAL & p->flags))
        !           827:                chara(p, ' ');
        !           828:
        !           829:        if ( ! (p->flags & TERMP_NONOSPACE))
        !           830:                p->flags &= ~TERMP_NOSPACE;
        !           831:
        !           832:        /*
        !           833:         * XXX - if literal and underlining, this will underline the
        !           834:         * spaces between literal words.
        !           835:         */
        !           836:
        !           837:        if (p->flags & TERMP_BOLD)
        !           838:                style(p, TERMSTYLE_BOLD);
        !           839:        if (p->flags & TERMP_UNDERLINE)
        !           840:                style(p, TERMSTYLE_UNDER);
        !           841:
        !           842:        for (i = 0; i < len; i++) {
        !           843:                if ('\\' == word[i]) {
        !           844:                        pescape(p, word, &i, len);
        !           845:                        continue;
        !           846:                }
        !           847:                chara(p, word[i]);
        !           848:        }
        !           849:
        !           850:        if (p->flags & TERMP_BOLD ||
        !           851:                        p->flags & TERMP_UNDERLINE)
        !           852:                style(p, TERMSTYLE_CLEAR);
        !           853: }
        !           854:
        !           855:
        !           856: /*
        !           857:  * Add a symbol to the output line buffer.
        !           858:  */
        !           859: static void
        !           860: symbola(struct termp *p, enum tsym sym)
        !           861: {
        !           862:
        !           863:        assert(p->symtab[sym].sym);
        !           864:        stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
        !           865: }
        !           866:
        !           867:
        !           868: /*
        !           869:  * Like chara() but for arbitrary-length buffers.  Resize the buffer by
        !           870:  * a factor of two (if the buffer is less than that) or the buffer's
        !           871:  * size.
        !           872:  */
        !           873: static void
        !           874: stringa(struct termp *p, const char *c, size_t sz)
        !           875: {
        !           876:        size_t           s;
        !           877:
        !           878:        s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
        !           879:
        !           880:        assert(c);
        !           881:        if (p->col + sz >= p->maxcols) {
        !           882:                p->buf = realloc(p->buf, s);
        !           883:                if (NULL == p->buf)
        !           884:                        err(1, "realloc");
        !           885:                p->maxcols = s;
        !           886:        }
        !           887:
        !           888:        (void)memcpy(&p->buf[p->col], c, sz);
        !           889:        p->col += sz;
        !           890: }
        !           891:
        !           892:
        !           893: /*
        !           894:  * Insert a single character into the line-buffer.  If the buffer's
        !           895:  * space is exceeded, then allocate more space by doubling the buffer
        !           896:  * size.
        !           897:  */
        !           898: static void
        !           899: chara(struct termp *p, char c)
        !           900: {
        !           901:
        !           902:        if (p->col + 1 >= p->maxcols) {
        !           903:                p->buf = realloc(p->buf, p->maxcols * 2);
        !           904:                if (NULL == p->buf)
        !           905:                        err(1, "malloc");
        !           906:                p->maxcols *= 2;
        !           907:        }
        !           908:        p->buf[(p->col)++] = c;
        !           909: }

CVSweb