nroff.c (44252B)
/* $Id$ */ /* * Copyright (c) 2008, Natacha Porté * Copyright (c) 2011, Vicent Martà * Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors * Copyright (c) 2016--2021 Kristaps Dzonsons <kristaps@bsd.lv> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.h" #if HAVE_SYS_QUEUE # include <sys/queue.h> #endif #include <assert.h> #include <ctype.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "lowdown.h" #include "extern.h" enum nfont { NFONT_ITALIC = 0, /* italic */ NFONT_BOLD, /* bold */ NFONT_FIXED, /* fixed-width */ NFONT__MAX }; struct nroff { struct hentryq headers_used; /* headers we've seen */ int man; /* whether man(7) */ int post_para; /* for choosing PP/LP */ unsigned int flags; /* output flags */ ssize_t headers_offs; /* header offset */ enum nfont fonts[NFONT__MAX]; /* see bqueue_font() */ struct bnodeq **foots; /* footnotes */ size_t footsz; /* footnote size */ }; enum bscope { BSCOPE_BLOCK = 0, BSCOPE_SPAN, BSCOPE_PDFHREF, BSCOPE_LITERAL, BSCOPE_FONT, BSCOPE_COLOUR }; /* * Instead of writing directly into the output buffer, we write * temporarily into bnodes, which are converted into output. These * nodes are aware of whether they need surrounding newlines. */ struct bnode { char *nbuf; /* (safe) 1st data */ char *buf; /* (unsafe) 2nd data */ char *nargs; /* (safe) 1st args */ char *args; /* (unsafe) 2nd args */ int close; /* BNODE_COLOUR/FONT */ int tblhack; /* BSCOPE_SPAN */ enum bscope scope; /* scope */ unsigned int font; /* if BNODE_FONT */ #define BFONT_ITALIC 0x01 #define BFONT_BOLD 0x02 #define BFONT_FIXED 0x04 unsigned int colour; /* if BNODE_COLOUR */ #define BFONT_BLUE 0x01 #define BFONT_RED 0x02 TAILQ_ENTRY(bnode) entries; }; TAILQ_HEAD(bnodeq, bnode); /* * Escape unsafe text into roff output such that no roff fetaures are * invoked by the text (macros, escapes, etc.). * If "oneline" is non-zero, newlines are replaced with spaces. * If "literal", doesn't strip leading space. * Return zero on failure, non-zero on success. */ static int hesc_nroff(struct lowdown_buf *ob, const char *data, size_t size, int oneline, int literal, int esc) { size_t i = 0; unsigned char ch; if (size == 0) return 1; if (!esc && oneline) { assert(!literal); for (i = 0; i < size; i++) { ch = data[i] == '\n' ? ' ' : data[i]; if (!hbuf_putc(ob, ch)) return 0; if (ch != ' ') continue; while (i < size && isspace((unsigned char)data[i])) i++; i--; } return 1; } else if (!esc) return hbuf_put(ob, data, size); /* Strip leading whitespace. */ if (!literal && ob->size > 0 && ob->data[ob->size - 1] == '\n') while (i < size && (data[i] == ' ' || data[i] == '\n')) i++; /* * According to mandoc_char(7), we need to escape the backtick, * single apostrophe, and tilde or else they'll be considered as * special Unicode output. * Slashes need to be escaped too. * We also escape double-quotes because this text might be used * within quoted macro arguments. */ for ( ; i < size; i++) switch (data[i]) { case '^': if (!HBUF_PUTSL(ob, "\\(ha")) return 0; break; case '~': if (!HBUF_PUTSL(ob, "\\(ti")) return 0; break; case '`': if (!HBUF_PUTSL(ob, "\\(ga")) return 0; break; case '"': if (!HBUF_PUTSL(ob, "\\(dq")) return 0; break; case '\n': if (!hbuf_putc(ob, oneline ? ' ' : '\n')) return 0; if (literal) break; /* Prevent leading spaces on the line. */ while (i + 1 < size && (data[i + 1] == ' ' || data[i + 1] == '\n')) i++; break; case '\\': if (!HBUF_PUTSL(ob, "\\e")) return 0; break; case '\'': case '.': if (!oneline && ob->size > 0 && ob->data[ob->size - 1] == '\n' && !HBUF_PUTSL(ob, "\\&")) return 0; /* FALLTHROUGH */ default: if (!hbuf_putc(ob, data[i])) return 0; break; } return 1; } static const char * nstate_colour_buf(unsigned int ft) { static char fonts[10]; fonts[0] = '\0'; if (ft == BFONT_BLUE) strlcat(fonts, "blue", sizeof(fonts)); else if (ft == BFONT_RED) strlcat(fonts, "red", sizeof(fonts)); else strlcat(fonts, "black", sizeof(fonts)); return fonts; } /* * For compatibility with traditional troff, return non-block font code * using the correct sequence of \fX, \f(xx, and \f[xxx]. */ static const char * nstate_font_buf(unsigned int ft, int blk) { static char fonts[10]; char *cp = fonts; size_t len = 0; if (ft & BFONT_FIXED) len++; if (ft & BFONT_BOLD) len++; if (ft & BFONT_ITALIC) len++; if (ft == 0) len++; if (!blk && len == 3) (*cp++) = '['; else if (!blk && len == 2) (*cp++) = '('; if (ft & BFONT_FIXED) (*cp++) = 'C'; if (ft & BFONT_BOLD) (*cp++) = 'B'; if (ft & BFONT_ITALIC) (*cp++) = 'I'; if (ft == 0) (*cp++) = 'R'; if (!blk && len == 3) (*cp++) = ']'; (*cp++) = '\0'; return fonts; } static int bqueue_colour(struct bnodeq *bq, enum lowdown_chng chng, int close) { struct bnode *bn; if ((bn = calloc(1, sizeof(struct bnode))) == NULL) return 0; TAILQ_INSERT_TAIL(bq, bn, entries); bn->scope = BSCOPE_COLOUR; bn->close = close; bn->colour = close ? 0 : chng == LOWDOWN_CHNG_INSERT ? BFONT_BLUE : BFONT_RED; return 1; } static int bqueue_font(const struct nroff *st, struct bnodeq *bq, int close) { struct bnode *bn; if ((bn = calloc(1, sizeof(struct bnode))) == NULL) return 0; TAILQ_INSERT_TAIL(bq, bn, entries); bn->scope = BSCOPE_FONT; bn->close = close; if (st->fonts[NFONT_FIXED]) bn->font |= BFONT_FIXED; if (st->fonts[NFONT_BOLD]) bn->font |= BFONT_BOLD; if (st->fonts[NFONT_ITALIC]) bn->font |= BFONT_ITALIC; return 1; } static struct bnode * bqueue_node(struct bnodeq *bq, enum bscope scope, const char *text) { struct bnode *bn; if ((bn = calloc(1, sizeof(struct bnode))) == NULL) return NULL; bn->scope = scope; if (text != NULL && (bn->nbuf = strdup(text)) == NULL) { free(bn); return NULL; } TAILQ_INSERT_TAIL(bq, bn, entries); return bn; } static struct bnode * bqueue_span(struct bnodeq *bq, const char *text) { return bqueue_node(bq, BSCOPE_SPAN, text); } static struct bnode * bqueue_block(struct bnodeq *bq, const char *text) { return bqueue_node(bq, BSCOPE_BLOCK, text); } static struct bnode * bqueue_semiblock(struct bnodeq *bq, const char *text) { return bqueue_node(bq, BSCOPE_PDFHREF, text); } static void bnode_free(struct bnode *bn) { free(bn->args); free(bn->nargs); free(bn->nbuf); free(bn->buf); free(bn); } static void bqueue_free(struct bnodeq *bq) { struct bnode *bn; while ((bn = TAILQ_FIRST(bq)) != NULL) { TAILQ_REMOVE(bq, bn, entries); bnode_free(bn); } } static void bqueue_strip_paras(struct bnodeq *bq) { struct bnode *bn; while ((bn = TAILQ_FIRST(bq)) != NULL) { if (bn->scope != BSCOPE_BLOCK || bn->nbuf == NULL) break; if (strcmp(bn->nbuf, ".PP") && strcmp(bn->nbuf, ".IP") && strcmp(bn->nbuf, ".LP")) break; TAILQ_REMOVE(bq, bn, entries); bnode_free(bn); } } static int bqueue_flush(struct lowdown_buf *ob, const struct bnodeq *bq, int esc) { const struct bnode *bn, *chk, *next; const char *cp; int nextblk; TAILQ_FOREACH(bn, bq, entries) { nextblk = 0; if (bn->scope == BSCOPE_PDFHREF && ob->size > 0 && ob->data[ob->size - 1] != '\n' && !hbuf_puts(ob, "\\c")) return 0; /* * Block scopes start with a newline. * Also have colours use their own block, as otherwise * (bugs in groff?) inline colour selection after a * hyperlink macro causes line breaks. * Besides, having spaces around changing colour, which * indicates differences, improves readability. */ if (bn->scope == BSCOPE_BLOCK || bn->scope == BSCOPE_PDFHREF || bn->scope == BSCOPE_COLOUR) { if (ob->size > 0 && ob->data[ob->size - 1] != '\n' && !hbuf_putc(ob, '\n')) return 0; nextblk = 1; } /* * Fonts can either be macros or inline depending upon * where they set relative to a macro block. */ if (bn->scope == BSCOPE_FONT) { chk = bn->close ? TAILQ_PREV(bn, bnodeq, entries) : TAILQ_NEXT(bn, entries); if (chk != NULL && (chk->scope == BSCOPE_PDFHREF || chk->scope == BSCOPE_BLOCK)) { if (ob->size > 0 && ob->data[ob->size - 1] != '\n' && !hbuf_putc(ob, '\n')) return 0; nextblk = 1; } } /* Print font and colour escapes. */ if (bn->scope == BSCOPE_FONT && nextblk) { if (!hbuf_printf(ob, ".ft %s", nstate_font_buf(bn->font, nextblk))) return 0; } else if (bn->scope == BSCOPE_FONT) { if (!hbuf_printf(ob, "\\f%s", nstate_font_buf(bn->font, nextblk))) return 0; } else if (bn->scope == BSCOPE_COLOUR) { assert(nextblk); if (!hbuf_printf(ob, ".gcolor %s", nstate_colour_buf(bn->colour))) return 0; } /* * A "tblhack" is used by a span macro to indicate * that it should start its own line, but that data * continues to flow after it. This is only used in * tables with T}, at this point. */ if (bn->scope == BSCOPE_SPAN && bn->tblhack && ob->size > 0 && ob->data[ob->size - 1] != '\n') if (!hbuf_putc(ob, '\n')) return 0; /* * If we're a span, double-check to see whether we * should introduce a line with an escape. */ if (bn->scope == BSCOPE_SPAN && bn->nbuf != NULL && ob->size > 0 && ob->data[ob->size - 1] == '\n' && (bn->nbuf[0] == '.' || bn->nbuf[0] == '\'') && !HBUF_PUTSL(ob, "\\&")) return 0; /* Safe data need not be escaped. */ if (bn->nbuf != NULL && !hbuf_puts(ob, bn->nbuf)) return 0; /* Unsafe data must be escaped. */ if (bn->scope == BSCOPE_LITERAL) { assert(bn->buf != NULL); if (!hesc_nroff(ob, bn->buf, strlen(bn->buf), 0, 1, esc)) return 0; } else if (bn->buf != NULL) if (!hesc_nroff(ob, bn->buf, strlen(bn->buf), 0, 0, esc)) return 0; if (bn->scope == BSCOPE_PDFHREF && (next = TAILQ_NEXT(bn, entries)) != NULL && next->scope == BSCOPE_SPAN && next->buf != NULL && next->buf[0] != ' ' && next->buf[0] != '\n' && !HBUF_PUTSL(ob, " -A \"\\c\"")) return 0; /* * Macro arguments follow after space. For links, these * must all be printed on the same line. */ if (bn->nargs != NULL && (bn->scope == BSCOPE_BLOCK || bn->scope == BSCOPE_PDFHREF)) { assert(nextblk); if (!hbuf_putc(ob, ' ')) return 0; for (cp = bn->nargs; *cp != '\0'; cp++) if (!hbuf_putc(ob, *cp == '\n' ? ' ' : *cp)) return 0; } if (bn->args != NULL) { assert(nextblk); assert(bn->scope == BSCOPE_BLOCK || bn->scope == BSCOPE_PDFHREF); if (!hbuf_putc(ob, ' ')) return 0; if (!hesc_nroff(ob, bn->args, strlen(bn->args), 1, 0, esc)) return 0; } /* Finally, trailing newline. */ if (nextblk && ob->size > 0 && ob->data[ob->size - 1] != '\n' && !hbuf_putc(ob, '\n')) return 0; } return 1; } /* * Convert a link into a short-link and place the escaped output into a * returned string. * Returns NULL on memory allocation failure. */ static char * hbuf2shortlink(const struct lowdown_buf *link) { struct lowdown_buf *tmp = NULL, *slink = NULL; char *ret = NULL; if ((tmp = hbuf_new(32)) == NULL) goto out; if ((slink = hbuf_new(32)) == NULL) goto out; if (!hbuf_shortlink(tmp, link)) goto out; if (!hesc_nroff(slink, tmp->data, tmp->size, 1, 0, 1)) goto out; ret = strndup(slink->data, slink->size); out: hbuf_free(tmp); hbuf_free(slink); return ret; } /* * Manage hypertext linking with the groff "pdfhref" macro or simply * using italics. XXX: use italics because the UR/UE macro doesn't * support leading un-spaced content, so "x[foo](https://foo.com)y" * wouldn't work. Until a solution is found, let's just italicise the * link text (or link, if no text is found). Return FALSE on error * (memory), TRUE on success. */ static int putlink(struct bnodeq *obq, struct nroff *st, const struct lowdown_buf *link, const struct lowdown_buf *id, struct bnodeq *bq, enum halink_type type) { struct lowdown_buf *ob = NULL; struct bnode *bn; size_t i; int rc = 0, local = 0; /* * For -Tman or without .pdfhref, format the link as-is, with * text then link, or use the various shorteners. */ if (st->man || !(st->flags & LOWDOWN_NROFF_GROFF)) { if (bq == NULL) { st->fonts[NFONT_ITALIC]++; if (!bqueue_font(st, obq, 0)) goto out; if ((bn = bqueue_span(obq, NULL)) == NULL) goto out; if (st->flags & LOWDOWN_NROFF_SHORTLINK) { bn->nbuf = hbuf2shortlink(link); if (bn->nbuf == NULL) goto out; } else { bn->buf = strndup(link->data, link->size); if (bn->buf == NULL) goto out; } st->fonts[NFONT_ITALIC]--; if (!bqueue_font(st, obq, 1)) goto out; rc = 1; goto out; } st->fonts[NFONT_BOLD]++; if (!bqueue_font(st, obq, 0)) goto out; TAILQ_CONCAT(obq, bq, entries); st->fonts[NFONT_BOLD]--; if (!bqueue_font(st, obq, 1)) goto out; if (st->flags & LOWDOWN_NROFF_NOLINK) { rc = 1; goto out; } if (bqueue_span(obq, " <") == NULL) goto out; st->fonts[NFONT_ITALIC]++; if (!bqueue_font(st, obq, 0)) goto out; if ((bn = bqueue_span(obq, NULL)) == NULL) goto out; if (st->flags & LOWDOWN_NROFF_SHORTLINK) { bn->nbuf = hbuf2shortlink(link); if (bn->nbuf == NULL) goto out; } else { bn->buf = strndup(link->data, link->size); if (bn->buf == NULL) goto out; } st->fonts[NFONT_ITALIC]--; if (!bqueue_font(st, obq, 1)) goto out; if (bqueue_span(obq, ">") == NULL) goto out; rc = 1; goto out; } /* Otherwise, use .pdfhref. */ if ((ob = hbuf_new(32)) == NULL) goto out; /* Encode the URL. */ local = type != HALINK_EMAIL && link->size && link->data[0] == '#'; if (!HBUF_PUTSL(ob, "-D ")) goto out; if (type == HALINK_EMAIL && !HBUF_PUTSL(ob, "mailto:")) goto out; for (i = local ? 1 : 0; i < link->size; i++) { if (!isprint((unsigned char)link->data[i]) || strchr("<>\\^`{|}\"", link->data[i]) != NULL) { if (!hbuf_printf(ob, "%%%.2X", link->data[i])) goto out; } else if (!hbuf_putc(ob, link->data[i])) goto out; } if (!HBUF_PUTSL(ob, " -- ")) goto out; if (bq == NULL && !hbuf_putb(ob, link)) goto out; else if (bq != NULL && !bqueue_flush(ob, bq, 1)) goto out; /* * If we have an ID, emit it before the link part. This is * important because this isn't printed, so using "-A \c" will * have no effect, so that's used on the subsequent link. */ if (id != NULL && id->size > 0) { bn = bqueue_semiblock(obq, ".pdfhref M"); if (bn == NULL) goto out; bn->args = strndup(id->data, id->size); if (bn->args == NULL) goto out; } /* Finally, emit the link contents. */ bn = local ? bqueue_semiblock(obq, ".pdfhref L") : bqueue_semiblock(obq, ".pdfhref W"); if (bn == NULL) goto out; if ((bn->nargs = strndup(ob->data, ob->size)) == NULL) goto out; rc = 1; out: hbuf_free(ob); return rc; } /* * Return FALSE on failure, TRUE on success. */ static int rndr_autolink(struct nroff *st, struct bnodeq *obq, const struct rndr_autolink *param) { return putlink(obq, st, ¶m->link, NULL, NULL, param->type); } static int rndr_blockcode(const struct nroff *st, struct bnodeq *obq, const struct rndr_blockcode *param) { struct bnode *bn; /* * XXX: intentionally don't use LD/DE because it introduces * vertical space. This means that subsequent blocks * (paragraphs, etc.) will have a double-newline. */ if (bqueue_block(obq, ".LP") == NULL) return 0; if (st->man && (st->flags & LOWDOWN_NROFF_GROFF)) { if (bqueue_block(obq, ".EX") == NULL) return 0; } else { if (bqueue_block(obq, ".nf") == NULL) return 0; if (bqueue_block(obq, ".ft CR") == NULL) return 0; } if ((bn = calloc(1, sizeof(struct bnode))) == NULL) return 0; TAILQ_INSERT_TAIL(obq, bn, entries); bn->scope = BSCOPE_LITERAL; bn->buf = strndup(param->text.data, param->text.size); if (bn->buf == NULL) return 0; if (st->man && (st->flags & LOWDOWN_NROFF_GROFF)) return bqueue_block(obq, ".EE") != NULL; if (bqueue_block(obq, ".ft") == NULL) return 0; return bqueue_block(obq, ".fi") != NULL; } static int rndr_definition_title(struct bnodeq *obq, struct bnodeq *bq) { if (bqueue_block(obq, ".LP") == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); return 1; } static int rndr_definition_data(struct bnodeq *obq, struct bnodeq *bq) { /* * The IP creates an empty vertical space til I figure out a * better way to do hanging lists, so account for it by backing * up first. * * XXX: this produces different results on mandoc and groff as * of 2022-02-19: mandoc backs up one space, while groff backs * up two. The groff behaviour is what we want, so that the * text is flush on the next line, but this is good enough. */ if (bqueue_block(obq, ".if n \\\n.sp -1v") == NULL) return 0; if (bqueue_block(obq, ".if t \\\n.sp -0.25v\n") == NULL) return 0; if (bqueue_block(obq, ".IP \"\" \\*(PI") == NULL) return 0; /* Strip out leading paragraphs. */ bqueue_strip_paras(bq); TAILQ_CONCAT(obq, bq, entries); return 1; } static int rndr_list(struct nroff *st, struct bnodeq *obq, const struct lowdown_node *n, struct bnodeq *bq) { /* * If we have a nested list, we need to use RS/RE to indent the * nested component. Otherwise the "IP" used for the titles and * contained paragraphs won't indent properly. */ for (n = n->parent; n != NULL; n = n->parent) if (n->type == LOWDOWN_LISTITEM) break; if (n != NULL && bqueue_block(obq, ".RS") == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); if (n != NULL && bqueue_block(obq, ".RE") == NULL) return 0; st->post_para = 1; return 1; } static int rndr_blockquote(struct nroff *st, struct bnodeq *obq, struct bnodeq *bq) { if (bqueue_block(obq, ".RS") == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); st->post_para = 1; return bqueue_block(obq, ".RE") != NULL; } static int rndr_codespan(struct bnodeq *obq, const struct rndr_codespan *param) { struct bnode *bn; if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; bn->buf = strndup(param->text.data, param->text.size); return bn->buf != NULL; } static int rndr_linebreak(struct bnodeq *obq) { return bqueue_block(obq, ".br") != NULL; } static int rndr_header(struct nroff *st, struct bnodeq *obq, struct bnodeq *bq, const struct lowdown_node *n) { ssize_t level; struct bnode *bn; struct lowdown_buf *buf = NULL; const struct lowdown_buf *nbuf; int rc = 0; level = (ssize_t)n->rndr_header.level + st->headers_offs; if (level < 1) level = 1; /* * For man(7), we use SH for the first-level section, SS for * other sections. TODO: use PP then italics or something for * third-level etc. */ if (st->man) { bn = level == 1 ? bqueue_block(obq, ".SH") : bqueue_block(obq, ".SS"); if (bn == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); st->post_para = 1; return 1; } /* * If we're using ms(7) w/groff extensions and w/o numbering, * used the numbered version of the SH macro. * If we're numbered ms(7), use NH. */ bn = (st->flags & LOWDOWN_NROFF_NUMBERED) ? bqueue_block(obq, ".NH") : bqueue_block(obq, ".SH"); if (bn == NULL) goto out; if ((st->flags & LOWDOWN_NROFF_NUMBERED) || (st->flags & LOWDOWN_NROFF_GROFF)) if (asprintf(&bn->nargs, "%zd", level) == -1) { bn->nargs = NULL; goto out; } TAILQ_CONCAT(obq, bq, entries); st->post_para = 1; /* * Used in -mspdf output for creating a TOC and intra-document * linking. */ if (st->flags & LOWDOWN_NROFF_GROFF) { if ((buf = hbuf_new(32)) == NULL) goto out; if (!hbuf_extract_text(buf, n)) goto out; if ((bn = bqueue_block(obq, ".pdfhref")) == NULL) goto out; if (asprintf(&bn->nargs, "O %zd", level) == -1) { bn->nargs = NULL; goto out; } /* * No need to quote: quotes will be converted by * escaping into roff. */ bn->args = strndup(buf->data, buf->size); if (bn->args == NULL) goto out; if ((bn = bqueue_block(obq, ".pdfhref M")) == NULL) goto out; /* * If the identifier comes from the user, we need to * escape it accordingly; otherwise, use it directly as * the hbuf_id() function will take care of it. */ if (n->rndr_header.attr_id.size) { bn->args = strndup (n->rndr_header.attr_id.data, n->rndr_header.attr_id.size); if (bn->args == NULL) goto out; } else { nbuf = hbuf_id(buf, NULL, &st->headers_used); if (nbuf == NULL) goto out; bn->nargs = strndup(nbuf->data, nbuf->size); if (bn->nargs == NULL) goto out; } } rc = 1; out: hbuf_free(buf); return rc; } /* * Return FALSE on failure, TRUE on success. */ static ssize_t rndr_link(struct nroff *st, struct bnodeq *obq, struct bnodeq *bq, const struct rndr_link *param) { return putlink(obq, st, ¶m->link, ¶m->attr_id, bq, HALINK_NORMAL); } static int rndr_listitem(struct bnodeq *obq, const struct lowdown_node *n, struct bnodeq *bq, const struct rndr_listitem *param) { struct bnode *bn; const char *box; if (param->flags & HLIST_FL_ORDERED) { if ((bn = bqueue_block(obq, ".IP")) == NULL) return 0; if (asprintf(&bn->nargs, "\"%zu. \"", param->num) == -1) return 0; } else if (param->flags & HLIST_FL_UNORDERED) { if (param->flags & HLIST_FL_CHECKED) box = "[u2611]"; else if (param->flags & HLIST_FL_UNCHECKED) box = "[u2610]"; else box = "(bu"; if ((bn = bqueue_block(obq, ".IP")) == NULL) return 0; if (asprintf(&bn->nargs, "\"\\%s\" 2", box) == -1) return 0; } /* Strip out all leading redundant paragraphs. */ bqueue_strip_paras(bq); TAILQ_CONCAT(obq, bq, entries); /* * Suppress trailing space if we're not in a block and there's a * list item that comes after us (i.e., anything after us). */ if ((n->rndr_listitem.flags & HLIST_FL_BLOCK) || (n->rndr_listitem.flags & HLIST_FL_DEF)) return 1; if (TAILQ_NEXT(n, entries) != NULL) { if (bqueue_block(obq, ".if n \\\n.sp -1") == NULL) return 0; if (bqueue_block(obq, ".if t \\\n.sp -0.25v\n") == NULL) return 0; } return 1; } static int rndr_paragraph(struct nroff *st, const struct lowdown_node *n, struct bnodeq *obq, struct bnodeq *nbq) { struct bnode *bn; /* * Subsequent paragraphs get a PP for the indentation; otherwise, use * LP and forego the indentation. If we're in a list item, make sure * that we don't reset our text indent by using an "IP". */ for ( ; n != NULL; n = n->parent) if (n->type == LOWDOWN_LISTITEM) break; if (n != NULL) bn = bqueue_block(obq, ".IP"); else if (st->post_para) bn = bqueue_block(obq, ".LP"); else bn = bqueue_block(obq, ".PP"); if (bn == NULL) return 0; TAILQ_CONCAT(obq, nbq, entries); st->post_para = 0; return 1; } static int rndr_raw_block(const struct nroff *st, struct bnodeq *obq, const struct rndr_blockhtml *param) { struct bnode *bn; if (st->flags & LOWDOWN_NROFF_SKIP_HTML) return 1; if ((bn = calloc(1, sizeof(struct bnode))) == NULL) return 0; TAILQ_INSERT_TAIL(obq, bn, entries); bn->scope = BSCOPE_LITERAL; bn->buf = strndup(param->text.data, param->text.size); return bn->buf != NULL; } static int rndr_hrule(struct nroff *st, struct bnodeq *obq) { /* The LP is to reset the margins. */ if (bqueue_block(obq, ".LP") == NULL) return 0; /* Set post_para so we get a following LP not PP. */ st->post_para = 1; if (st->man) return bqueue_block(obq, "\\l\'2i'") != NULL; return bqueue_block(obq, ".ie d HR \\{\\\n" ".HR\n" "\\}\n" ".el \\{\\\n" ".sp 1v\n" "\\l'\\n(.lu'\n" ".sp 1v\n" ".\\}") != NULL; } static int rndr_image(struct nroff *st, struct bnodeq *obq, const struct rndr_image *param) { const char *cp; size_t sz; struct bnode *bn; if (!st->man) { cp = memrchr(param->link.data, '.', param->link.size); if (cp != NULL) { cp++; sz = param->link.size - (cp - param->link.data); if ((sz == 2 && memcmp(cp, "ps", 2) == 0) || (sz == 3 && memcmp(cp, "eps", 3) == 0)) { bn = bqueue_block(obq, ".PSPIC"); if (bn == NULL) return 0; bn->args = strndup(param->link.data, param->link.size); return bn->args != NULL; } } } /* In -Tman, we have no images: treat as a link. */ st->fonts[NFONT_BOLD]++; if (!bqueue_font(st, obq, 0)) return 0; if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; bn->buf = strndup(param->alt.data, param->alt.size); if (bn->buf == NULL) return 0; st->fonts[NFONT_BOLD]--; if (!bqueue_font(st, obq, 1)) return 0; if (st->flags & LOWDOWN_NROFF_NOLINK) return bqueue_span(obq, " (Image)") != NULL; if (bqueue_span(obq, " (Image: ") == NULL) return 0; st->fonts[NFONT_ITALIC]++; if (!bqueue_font(st, obq, 0)) return 0; if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; if (st->flags & LOWDOWN_NROFF_SHORTLINK) { bn->nbuf = hbuf2shortlink(¶m->link); if (bn->nbuf == NULL) return 0; } else { bn->buf = strndup(param->link.data, param->link.size); if (bn->buf == NULL) return 0; } st->fonts[NFONT_ITALIC]--; if (!bqueue_font(st, obq, 1)) return 0; return bqueue_span(obq, ")") != NULL; } static int rndr_raw_html(const struct nroff *st, struct bnodeq *obq, const struct rndr_raw_html *param) { struct bnode *bn; if (st->flags & LOWDOWN_NROFF_SKIP_HTML) return 1; if ((bn = calloc(1, sizeof(struct bnode))) == NULL) return 0; TAILQ_INSERT_TAIL(obq, bn, entries); bn->scope = BSCOPE_LITERAL; bn->buf = strndup(param->text.data, param->text.size); return bn->buf != NULL; } static int rndr_table(struct nroff *st, struct bnodeq *obq, struct bnodeq *bq) { const char *macro; macro = st->man || !(st->flags & LOWDOWN_NROFF_GROFF) ? ".TS" : ".TS H"; if (bqueue_block(obq, macro) == NULL) return 0; if (bqueue_block(obq, "tab(|) expand allbox;") == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); st->post_para = 1; return bqueue_block(obq, ".TE") != NULL; } static int rndr_table_header(const struct nroff *st, struct bnodeq *obq, struct bnodeq *bq, const struct rndr_table_header *param) { size_t i; struct lowdown_buf *ob; struct bnode *bn; int rc = 0; if ((ob = hbuf_new(32)) == NULL) return 0; /* * This specifies the header layout. * We make the header bold, but this is arbitrary. */ if ((bn = bqueue_block(obq, NULL)) == NULL) goto out; for (i = 0; i < param->columns; i++) { if (i > 0 && !HBUF_PUTSL(ob, " ")) goto out; switch (param->flags[i] & HTBL_FL_ALIGNMASK) { case HTBL_FL_ALIGN_CENTER: if (!HBUF_PUTSL(ob, "cb")) goto out; break; case HTBL_FL_ALIGN_RIGHT: if (!HBUF_PUTSL(ob, "rb")) goto out; break; default: if (!HBUF_PUTSL(ob, "lb")) goto out; break; } } if ((bn->nbuf = strndup(ob->data, ob->size)) == NULL) goto out; /* Now the body layout. */ hbuf_truncate(ob); if ((bn = bqueue_block(obq, NULL)) == NULL) goto out; for (i = 0; i < param->columns; i++) { if (i > 0 && !HBUF_PUTSL(ob, " ")) goto out; switch (param->flags[i] & HTBL_FL_ALIGNMASK) { case HTBL_FL_ALIGN_CENTER: if (!HBUF_PUTSL(ob, "c")) goto out; break; case HTBL_FL_ALIGN_RIGHT: if (!HBUF_PUTSL(ob, "r")) goto out; break; default: if (!HBUF_PUTSL(ob, "l")) goto out; break; } } if (!hbuf_putc(ob, '.')) goto out; if ((bn->nbuf = strndup(ob->data, ob->size)) == NULL) goto out; TAILQ_CONCAT(obq, bq, entries); if (!st->man && (st->flags & LOWDOWN_NROFF_GROFF) && bqueue_block(obq, ".TH") == NULL) goto out; rc = 1; out: hbuf_free(ob); return rc; } static int rndr_table_row(struct bnodeq *obq, struct bnodeq *bq) { TAILQ_CONCAT(obq, bq, entries); return bqueue_block(obq, NULL) != NULL; } static int rndr_table_cell(struct bnodeq *obq, struct bnodeq *bq, const struct rndr_table_cell *param) { struct bnode *bn; if (param->col > 0 && bqueue_span(obq, "|") == NULL) return 0; if (bqueue_span(obq, "T{\n") == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); if ((bn = bqueue_span(obq, "T}")) == NULL) return 0; bn->tblhack = 1; return 1; } static int rndr_superscript(struct bnodeq *obq, struct bnodeq *bq) { if (bqueue_span(obq, "\\u\\s-3") == NULL) return 0; TAILQ_CONCAT(obq, bq, entries); return bqueue_span(obq, "\\s+3\\d") != NULL; } static int rndr_footnote_def(const struct nroff *st, struct bnodeq *obq, struct bnodeq *bq, size_t num) { struct bnode *bn; /* * Use groff_ms(7)-style footnotes. * We know that the definitions are delivered in the same order * as the footnotes are made, so we can use the automatic * ordering facilities. */ if (!st->man) { if (bqueue_block(obq, ".FS") == NULL) return 0; bqueue_strip_paras(bq); TAILQ_CONCAT(obq, bq, entries); return bqueue_block(obq, ".FE") != NULL; } /* * For man(7), just print as normal, with a leading footnote * number in italics and superscripted. */ if (bqueue_block(obq, ".LP") == NULL) return 0; if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; if (asprintf(&bn->nbuf, "\\0\\fI\\u\\s-3%zu\\s+3\\d\\fP\\0", num) == -1) { bn->nbuf = NULL; return 0; } bqueue_strip_paras(bq); TAILQ_CONCAT(obq, bq, entries); return 1; } static int rndr_footnotes(const struct nroff *st, struct bnodeq *obq) { size_t i; if (st->footsz == 0) return 1; if (st->man) { if (bqueue_block(obq, ".LP") == NULL) return 0; if (bqueue_block(obq, ".sp 3") == NULL) return 0; if (bqueue_block(obq, "\\l\'2i'") == NULL) return 0; } for (i = 0; i < st->footsz; i++) if (!rndr_footnote_def(st, obq, st->foots[i], i + 1)) return 0; return 1; } static int rndr_footnote_ref(struct nroff *st, struct bnodeq *obq, struct bnodeq *bq) { struct bnode *bn; void *pp; size_t num = st->footsz; /* * Use groff_ms(7)-style automatic footnoting, else just put a * reference number in small superscripts. */ if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; if (!st->man) bn->nbuf = strdup("\\**"); else if (asprintf(&bn->nbuf, "\\u\\s-3%zu\\s+3\\d", num + 1) == -1) bn->nbuf = NULL; if (bn->nbuf == NULL) return 0; /* * For -Tman, queue the footnote for printing at the end of the * document. For -Tms, emit it now in a FS/FE block. */ if (st->man) { pp = recallocarray(st->foots, st->footsz, st->footsz + 1, sizeof(struct bnodeq *)); if (pp == NULL) return 0; st->foots = pp; st->foots[st->footsz++] = malloc(sizeof(struct bnodeq)); if (st->foots[num] == NULL) return 0; TAILQ_INIT(st->foots[num]); TAILQ_CONCAT(st->foots[num], bq, entries); return 1; } else { if (bqueue_block(obq, ".FS") == NULL) return 0; bqueue_strip_paras(bq); TAILQ_CONCAT(obq, bq, entries); return bqueue_block(obq, ".FE") != NULL; } } static int rndr_entity(const struct nroff *st, struct bnodeq *obq, const struct rndr_entity *param) { char buf[32]; const char *ent; struct bnode *bn; int32_t iso; size_t sz; /* * Handle named entities if "ent" is non-NULL, use unicode * escapes for values above 126, and just the regular character * if within the ASCII set. */ if ((ent = entity_find_nroff(¶m->text, &iso)) != NULL) { sz = strlen(ent); if (sz == 1) snprintf(buf, sizeof(buf), "\\%s", ent); else if (sz == 2) snprintf(buf, sizeof(buf), "\\(%s", ent); else snprintf(buf, sizeof(buf), "\\[%s]", ent); return bqueue_span(obq, buf) != NULL; } else if (iso > 0 && iso > 126) { if (st->flags & LOWDOWN_NROFF_GROFF) snprintf(buf, sizeof(buf), "\\[u%.4llX]", (unsigned long long)iso); else snprintf(buf, sizeof(buf), "\\U\'%.4llX\'", (unsigned long long)iso); return bqueue_span(obq, buf) != NULL; } else if (iso > 0) { snprintf(buf, sizeof(buf), "%c", iso); return bqueue_span(obq, buf) != NULL; } if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; bn->buf = strndup(param->text.data, param->text.size); return bn->buf != NULL; } /* * Split "b" at sequential white-space, outputting the results in the * line-based "env" macro. The content in "b" has already been escaped. */ static int rndr_meta_multi(struct bnodeq *obq, const char *b, const char *env) { const char *start; size_t sz, i, bsz; struct bnode *bn; char macro[32]; if (b == NULL) return 1; assert(strlen(env) < sizeof(macro) - 1); snprintf(macro, sizeof(macro), ".%s", env); bsz = strlen(b); for (i = 0; i < bsz; i++) { while (i < bsz && isspace((unsigned char)b[i])) i++; if (i == bsz) continue; start = &b[i]; for (; i < bsz; i++) if (i < bsz - 1 && isspace((unsigned char)b[i]) && isspace((unsigned char)b[i + 1])) break; if ((sz = &b[i] - start) == 0) continue; if (bqueue_block(obq, macro) == NULL) return 0; if ((bn = bqueue_span(obq, NULL)) == NULL) return 0; if ((bn->nbuf = strndup(start, sz)) == NULL) return 0; } return 1; } /* * Fill "mq" by serialising child nodes into strings. The serialised * strings are escaped. */ static int rndr_meta(struct nroff *st, const struct bnodeq *bq, struct lowdown_metaq *mq, const struct rndr_meta *params) { struct lowdown_meta *m; struct lowdown_buf *ob; ssize_t val; const char *ep; if ((m = calloc(1, sizeof(struct lowdown_meta))) == NULL) return 0; TAILQ_INSERT_TAIL(mq, m, entries); m->key = strndup(params->key.data, params->key.size); if (m->key == NULL) return 0; if ((ob = hbuf_new(32)) == NULL) return 0; if (!bqueue_flush(ob, bq, 1)) { hbuf_free(ob); return 0; } m->value = strndup(ob->data, ob->size); hbuf_free(ob); if (m->value == NULL) return 0; if (strcmp(m->key, "shiftheadinglevelby") == 0) { val = (ssize_t)strtonum (m->value, -100, 100, &ep); if (ep == NULL) st->headers_offs = val + 1; } else if (strcmp(m->key, "baseheaderlevel") == 0) { val = (ssize_t)strtonum (m->value, 1, 100, &ep); if (ep == NULL) st->headers_offs = val; } return 1; } static int rndr_doc_header(const struct nroff *st, struct bnodeq *obq, const struct lowdown_metaq *mq) { struct lowdown_buf *ob = NULL; struct bnode *bn; const struct lowdown_meta *m; int rc = 0; const char *author = NULL, *title = NULL, *affil = NULL, *date = NULL, *copy = NULL, *sec = NULL, *rcsauthor = NULL, *rcsdate = NULL, *source = NULL, *volume = NULL; if (!(st->flags & LOWDOWN_STANDALONE)) return 1; TAILQ_FOREACH(m, mq, entries) if (strcasecmp(m->key, "author") == 0) author = m->value; else if (strcasecmp(m->key, "copyright") == 0) copy = m->value; else if (strcasecmp(m->key, "affiliation") == 0) affil = m->value; else if (strcasecmp(m->key, "date") == 0) date = m->value; else if (strcasecmp(m->key, "rcsauthor") == 0) rcsauthor = rcsauthor2str(m->value); else if (strcasecmp(m->key, "rcsdate") == 0) rcsdate = rcsdate2str(m->value); else if (strcasecmp(m->key, "title") == 0) title = m->value; else if (strcasecmp(m->key, "section") == 0) sec = m->value; else if (strcasecmp(m->key, "source") == 0) source = m->value; else if (strcasecmp(m->key, "volume") == 0) volume = m->value; /* Overrides. */ if (title == NULL) title = "Untitled article"; if (sec == NULL) sec = "7"; if (rcsdate != NULL) date = rcsdate; if (rcsauthor != NULL) author = rcsauthor; bn = bqueue_block(obq, ".\\\" -*- mode: troff; coding: utf-8 -*-"); if (bn == NULL) goto out; if (!st->man) { if (copy != NULL) { bn = bqueue_block(obq, ".ds LF Copyright \\(co"); if (bn == NULL) goto out; if ((bn->nargs = strdup(copy)) == NULL) goto out; } if (date != NULL) { if (copy != NULL) bn = bqueue_block(obq, ".ds RF"); else bn = bqueue_block(obq, ".DA"); if (bn == NULL) goto out; if ((bn->nargs = strdup(date)) == NULL) goto out; } if (bqueue_block(obq, ".TL") == NULL) goto out; if ((bn = bqueue_span(obq, NULL)) == NULL) goto out; if ((bn->nbuf = strdup(title)) == NULL) goto out; if (!rndr_meta_multi(obq, author, "AU")) goto out; if (!rndr_meta_multi(obq, affil, "AI")) goto out; } else { if ((ob = hbuf_new(32)) == NULL) goto out; /* * The syntax of this macro, according to man(7), is * TH name section date [source [volume]]. */ if ((bn = bqueue_block(obq, ".TH")) == NULL) goto out; if (!hbuf_putc(ob, '"') || !hesc_nroff(ob, title, strlen(title), 1, 0, 0) || !HBUF_PUTSL(ob, "\" \"") || !hesc_nroff(ob, sec, strlen(sec), 1, 0, 0) || !hbuf_putc(ob, '"')) goto out; /* * We may not have a date (or it may be empty), in which * case man(7) says the current date is used. */ if (!HBUF_PUTSL(ob, " \"")) goto out; if (date != NULL && !hesc_nroff(ob, date, strlen(date), 1, 0, 0)) goto out; if (!HBUF_PUTSL(ob, "\"")) goto out; /* * Don't print these unless necessary, as the latter * overrides the default system printing for the * section. */ if (source != NULL || volume != NULL) { if (!HBUF_PUTSL(ob, " \"")) goto out; if (source != NULL && !hesc_nroff (ob, source, strlen(source), 1, 0, 0)) goto out; if (!HBUF_PUTSL(ob, "\"")) goto out; if (!HBUF_PUTSL(ob, " \"")) goto out; if (volume != NULL && !hesc_nroff (ob, volume, strlen(volume), 1, 0, 0)) goto out; if (!HBUF_PUTSL(ob, "\"")) goto out; } if ((bn->nargs = strndup(ob->data, ob->size)) == NULL) goto out; } rc = 1; out: hbuf_free(ob); return rc; } /* * Actually render the node "n" and all of its children into the output * buffer "ob", chopping "chop" from the current node if specified. * Return what (if anything) we should chop from the next node or <0 on * failure. */ static int rndr(struct lowdown_metaq *mq, struct nroff *st, const struct lowdown_node *n, struct bnodeq *obq) { const struct lowdown_node *child; int rc = 1; enum nfont fonts[NFONT__MAX]; struct bnodeq tmpbq; struct bnode *bn; TAILQ_INIT(&tmpbq); if ((n->chng == LOWDOWN_CHNG_INSERT || n->chng == LOWDOWN_CHNG_DELETE) && !bqueue_colour(obq, n->chng, 0)) goto out; /* * Font management. * roff doesn't handle its own font stack, so we can't set fonts * and step out of them in a nested way. */ memcpy(fonts, st->fonts, sizeof(fonts)); switch (n->type) { case LOWDOWN_CODESPAN: st->fonts[NFONT_FIXED]++; if (!bqueue_font(st, obq, 0)) goto out; break; case LOWDOWN_EMPHASIS: st->fonts[NFONT_ITALIC]++; if (!bqueue_font(st, obq, 0)) goto out; break; case LOWDOWN_HIGHLIGHT: case LOWDOWN_DOUBLE_EMPHASIS: st->fonts[NFONT_BOLD]++; if (!bqueue_font(st, obq, 0)) goto out; break; case LOWDOWN_TRIPLE_EMPHASIS: st->fonts[NFONT_ITALIC]++; st->fonts[NFONT_BOLD]++; if (!bqueue_font(st, obq, 0)) goto out; break; default: break; } TAILQ_FOREACH(child, &n->children, entries) if (!rndr(mq, st, child, &tmpbq)) goto out; switch (n->type) { case LOWDOWN_BLOCKCODE: rc = rndr_blockcode(st, obq, &n->rndr_blockcode); break; case LOWDOWN_BLOCKQUOTE: rc = rndr_blockquote(st, obq, &tmpbq); break; case LOWDOWN_DEFINITION: rc = rndr_list(st, obq, n, &tmpbq); break; case LOWDOWN_DEFINITION_DATA: rc = rndr_definition_data(obq, &tmpbq); break; case LOWDOWN_DEFINITION_TITLE: rc = rndr_definition_title(obq, &tmpbq); break; case LOWDOWN_DOC_HEADER: rc = rndr_doc_header(st, obq, mq); break; case LOWDOWN_META: if (n->chng != LOWDOWN_CHNG_DELETE) rc = rndr_meta(st, &tmpbq, mq, &n->rndr_meta); break; case LOWDOWN_HEADER: rc = rndr_header(st, obq, &tmpbq, n); break; case LOWDOWN_HRULE: rc = rndr_hrule(st, obq); break; case LOWDOWN_LIST: rc = rndr_list(st, obq, n, &tmpbq); break; case LOWDOWN_LISTITEM: rc = rndr_listitem(obq, n, &tmpbq, &n->rndr_listitem); break; case LOWDOWN_PARAGRAPH: rc = rndr_paragraph(st, n, obq, &tmpbq); break; case LOWDOWN_TABLE_BLOCK: rc = rndr_table(st, obq, &tmpbq); break; case LOWDOWN_TABLE_HEADER: rc = rndr_table_header(st, obq, &tmpbq, &n->rndr_table_header); break; case LOWDOWN_TABLE_ROW: rc = rndr_table_row(obq, &tmpbq); break; case LOWDOWN_TABLE_CELL: rc = rndr_table_cell(obq, &tmpbq, &n->rndr_table_cell); break; case LOWDOWN_ROOT: TAILQ_CONCAT(obq, &tmpbq, entries); rc = rndr_footnotes(st, obq); break; case LOWDOWN_BLOCKHTML: rc = rndr_raw_block(st, obq, &n->rndr_blockhtml); break; case LOWDOWN_LINK_AUTO: rc = rndr_autolink(st, obq, &n->rndr_autolink); break; case LOWDOWN_CODESPAN: rc = rndr_codespan(obq, &n->rndr_codespan); break; case LOWDOWN_IMAGE: rc = rndr_image(st, obq, &n->rndr_image); break; case LOWDOWN_LINEBREAK: rc = rndr_linebreak(obq); break; case LOWDOWN_LINK: rc = rndr_link(st, obq, &tmpbq, &n->rndr_link); break; case LOWDOWN_SUPERSCRIPT: rc = rndr_superscript(obq, &tmpbq); break; case LOWDOWN_FOOTNOTE: rc = rndr_footnote_ref(st, obq, &tmpbq); break; case LOWDOWN_RAW_HTML: rc = rndr_raw_html(st, obq, &n->rndr_raw_html); break; case LOWDOWN_NORMAL_TEXT: if ((bn = bqueue_span(obq, NULL)) == NULL) goto out; bn->buf = strndup (n->rndr_normal_text.text.data, n->rndr_normal_text.text.size); if (bn->buf == NULL) goto out; break; case LOWDOWN_ENTITY: rc = rndr_entity(st, obq, &n->rndr_entity); break; default: TAILQ_CONCAT(obq, &tmpbq, entries); break; } if (!rc) goto out; /* Restore the font stack. */ switch (n->type) { case LOWDOWN_CODESPAN: case LOWDOWN_EMPHASIS: case LOWDOWN_HIGHLIGHT: case LOWDOWN_DOUBLE_EMPHASIS: case LOWDOWN_TRIPLE_EMPHASIS: memcpy(st->fonts, fonts, sizeof(fonts)); if (!bqueue_font(st, obq, 1)) { rc = 0; goto out; } break; default: break; } if ((n->chng == LOWDOWN_CHNG_INSERT || n->chng == LOWDOWN_CHNG_DELETE) && !bqueue_colour(obq, n->chng, 1)) { rc = 0; goto out; } rc = 1; out: bqueue_free(&tmpbq); return rc; } int lowdown_nroff_rndr(struct lowdown_buf *ob, void *arg, const struct lowdown_node *n) { struct nroff *st = arg; struct lowdown_metaq metaq; int rc = 0; struct bnodeq bq; size_t i; TAILQ_INIT(&metaq); TAILQ_INIT(&bq); TAILQ_INIT(&st->headers_used); memset(st->fonts, 0, sizeof(st->fonts)); st->headers_offs = 1; st->post_para = 0; if (rndr(&metaq, st, n, &bq)) { if (!bqueue_flush(ob, &bq, 1)) goto out; if (ob->size && ob->data[ob->size - 1] != '\n' && !hbuf_putc(ob, '\n')) goto out; rc = 1; } out: for (i = 0; i < st->footsz; i++) { bqueue_free(st->foots[i]); free(st->foots[i]); } free(st->foots); st->footsz = 0; st->foots = NULL; lowdown_metaq_free(&metaq); bqueue_free(&bq); hentryq_clear(&st->headers_used); return rc; } void * lowdown_nroff_new(const struct lowdown_opts *opts) { struct nroff *p; if ((p = calloc(1, sizeof(struct nroff))) == NULL) return NULL; p->flags = opts != NULL ? opts->oflags : 0; p->man = opts != NULL && opts->type == LOWDOWN_MAN; return p; } void lowdown_nroff_free(void *arg) { /* No need to check NULL: pass directly to free(). */ free(arg); }