usr.sbin/makefs: Add -o c|C option to specify comp|check type
[dragonfly.git] / contrib / mdocml / mdoc_markdown.c
blob63d8e17055804b043f01f0fb89807ab98ee12b31
1 /* $Id: mdoc_markdown.c,v 1.37 2021/08/10 12:55:03 schwarze Exp $ */
2 /*
3 * Copyright (c) 2017, 2018, 2020 Ingo Schwarze <schwarze@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 * Markdown formatter for mdoc(7) used by mandoc(1).
19 #include "config.h"
21 #include <sys/types.h>
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "mdoc.h"
33 #include "main.h"
35 struct md_act {
36 int (*cond)(struct roff_node *);
37 int (*pre)(struct roff_node *);
38 void (*post)(struct roff_node *);
39 const char *prefix; /* pre-node string constant */
40 const char *suffix; /* post-node string constant */
43 static void md_nodelist(struct roff_node *);
44 static void md_node(struct roff_node *);
45 static const char *md_stack(char);
46 static void md_preword(void);
47 static void md_rawword(const char *);
48 static void md_word(const char *);
49 static void md_named(const char *);
50 static void md_char(unsigned char);
51 static void md_uri(const char *);
53 static int md_cond_head(struct roff_node *);
54 static int md_cond_body(struct roff_node *);
56 static int md_pre_abort(struct roff_node *);
57 static int md_pre_raw(struct roff_node *);
58 static int md_pre_word(struct roff_node *);
59 static int md_pre_skip(struct roff_node *);
60 static void md_pre_syn(struct roff_node *);
61 static int md_pre_An(struct roff_node *);
62 static int md_pre_Ap(struct roff_node *);
63 static int md_pre_Bd(struct roff_node *);
64 static int md_pre_Bk(struct roff_node *);
65 static int md_pre_Bl(struct roff_node *);
66 static int md_pre_D1(struct roff_node *);
67 static int md_pre_Dl(struct roff_node *);
68 static int md_pre_En(struct roff_node *);
69 static int md_pre_Eo(struct roff_node *);
70 static int md_pre_Fa(struct roff_node *);
71 static int md_pre_Fd(struct roff_node *);
72 static int md_pre_Fn(struct roff_node *);
73 static int md_pre_Fo(struct roff_node *);
74 static int md_pre_In(struct roff_node *);
75 static int md_pre_It(struct roff_node *);
76 static int md_pre_Lk(struct roff_node *);
77 static int md_pre_Mt(struct roff_node *);
78 static int md_pre_Nd(struct roff_node *);
79 static int md_pre_Nm(struct roff_node *);
80 static int md_pre_No(struct roff_node *);
81 static int md_pre_Ns(struct roff_node *);
82 static int md_pre_Pp(struct roff_node *);
83 static int md_pre_Rs(struct roff_node *);
84 static int md_pre_Sh(struct roff_node *);
85 static int md_pre_Sm(struct roff_node *);
86 static int md_pre_Vt(struct roff_node *);
87 static int md_pre_Xr(struct roff_node *);
88 static int md_pre__T(struct roff_node *);
89 static int md_pre_br(struct roff_node *);
91 static void md_post_raw(struct roff_node *);
92 static void md_post_word(struct roff_node *);
93 static void md_post_pc(struct roff_node *);
94 static void md_post_Bk(struct roff_node *);
95 static void md_post_Bl(struct roff_node *);
96 static void md_post_D1(struct roff_node *);
97 static void md_post_En(struct roff_node *);
98 static void md_post_Eo(struct roff_node *);
99 static void md_post_Fa(struct roff_node *);
100 static void md_post_Fd(struct roff_node *);
101 static void md_post_Fl(struct roff_node *);
102 static void md_post_Fn(struct roff_node *);
103 static void md_post_Fo(struct roff_node *);
104 static void md_post_In(struct roff_node *);
105 static void md_post_It(struct roff_node *);
106 static void md_post_Lb(struct roff_node *);
107 static void md_post_Nm(struct roff_node *);
108 static void md_post_Pf(struct roff_node *);
109 static void md_post_Vt(struct roff_node *);
110 static void md_post__T(struct roff_node *);
112 static const struct md_act md_acts[MDOC_MAX - MDOC_Dd] = {
113 { NULL, NULL, NULL, NULL, NULL }, /* Dd */
114 { NULL, NULL, NULL, NULL, NULL }, /* Dt */
115 { NULL, NULL, NULL, NULL, NULL }, /* Os */
116 { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Sh */
117 { NULL, md_pre_Sh, NULL, NULL, NULL }, /* Ss */
118 { NULL, md_pre_Pp, NULL, NULL, NULL }, /* Pp */
119 { md_cond_body, md_pre_D1, md_post_D1, NULL, NULL }, /* D1 */
120 { md_cond_body, md_pre_Dl, md_post_D1, NULL, NULL }, /* Dl */
121 { md_cond_body, md_pre_Bd, md_post_D1, NULL, NULL }, /* Bd */
122 { NULL, NULL, NULL, NULL, NULL }, /* Ed */
123 { md_cond_body, md_pre_Bl, md_post_Bl, NULL, NULL }, /* Bl */
124 { NULL, NULL, NULL, NULL, NULL }, /* El */
125 { NULL, md_pre_It, md_post_It, NULL, NULL }, /* It */
126 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ad */
127 { NULL, md_pre_An, NULL, NULL, NULL }, /* An */
128 { NULL, md_pre_Ap, NULL, NULL, NULL }, /* Ap */
129 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Ar */
130 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cd */
131 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Cm */
132 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Dv */
133 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Er */
134 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Ev */
135 { NULL, NULL, NULL, NULL, NULL }, /* Ex */
136 { NULL, md_pre_Fa, md_post_Fa, NULL, NULL }, /* Fa */
137 { NULL, md_pre_Fd, md_post_Fd, "**", "**" }, /* Fd */
138 { NULL, md_pre_raw, md_post_Fl, "**-", "**" }, /* Fl */
139 { NULL, md_pre_Fn, md_post_Fn, NULL, NULL }, /* Fn */
140 { NULL, md_pre_Fd, md_post_raw, "*", "*" }, /* Ft */
141 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ic */
142 { NULL, md_pre_In, md_post_In, NULL, NULL }, /* In */
143 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Li */
144 { md_cond_head, md_pre_Nd, NULL, NULL, NULL }, /* Nd */
145 { NULL, md_pre_Nm, md_post_Nm, "**", "**" }, /* Nm */
146 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Op */
147 { NULL, md_pre_abort, NULL, NULL, NULL }, /* Ot */
148 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Pa */
149 { NULL, NULL, NULL, NULL, NULL }, /* Rv */
150 { NULL, NULL, NULL, NULL, NULL }, /* St */
151 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Va */
152 { NULL, md_pre_Vt, md_post_Vt, "*", "*" }, /* Vt */
153 { NULL, md_pre_Xr, NULL, NULL, NULL }, /* Xr */
154 { NULL, NULL, md_post_pc, NULL, NULL }, /* %A */
155 { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %B */
156 { NULL, NULL, md_post_pc, NULL, NULL }, /* %D */
157 { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %I */
158 { NULL, md_pre_raw, md_post_pc, "*", "*" }, /* %J */
159 { NULL, NULL, md_post_pc, NULL, NULL }, /* %N */
160 { NULL, NULL, md_post_pc, NULL, NULL }, /* %O */
161 { NULL, NULL, md_post_pc, NULL, NULL }, /* %P */
162 { NULL, NULL, md_post_pc, NULL, NULL }, /* %R */
163 { NULL, md_pre__T, md_post__T, NULL, NULL }, /* %T */
164 { NULL, NULL, md_post_pc, NULL, NULL }, /* %V */
165 { NULL, NULL, NULL, NULL, NULL }, /* Ac */
166 { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Ao */
167 { md_cond_body, md_pre_word, md_post_word, "<", ">" }, /* Aq */
168 { NULL, NULL, NULL, NULL, NULL }, /* At */
169 { NULL, NULL, NULL, NULL, NULL }, /* Bc */
170 { NULL, NULL, NULL, NULL, NULL }, /* Bf XXX not implemented */
171 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bo */
172 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Bq */
173 { NULL, NULL, NULL, NULL, NULL }, /* Bsx */
174 { NULL, NULL, NULL, NULL, NULL }, /* Bx */
175 { NULL, NULL, NULL, NULL, NULL }, /* Db */
176 { NULL, NULL, NULL, NULL, NULL }, /* Dc */
177 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Do */
178 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Dq */
179 { NULL, NULL, NULL, NULL, NULL }, /* Ec */
180 { NULL, NULL, NULL, NULL, NULL }, /* Ef */
181 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Em */
182 { md_cond_body, md_pre_Eo, md_post_Eo, NULL, NULL }, /* Eo */
183 { NULL, NULL, NULL, NULL, NULL }, /* Fx */
184 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Ms */
185 { NULL, md_pre_No, NULL, NULL, NULL }, /* No */
186 { NULL, md_pre_Ns, NULL, NULL, NULL }, /* Ns */
187 { NULL, NULL, NULL, NULL, NULL }, /* Nx */
188 { NULL, NULL, NULL, NULL, NULL }, /* Ox */
189 { NULL, NULL, NULL, NULL, NULL }, /* Pc */
190 { NULL, NULL, md_post_Pf, NULL, NULL }, /* Pf */
191 { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Po */
192 { md_cond_body, md_pre_word, md_post_word, "(", ")" }, /* Pq */
193 { NULL, NULL, NULL, NULL, NULL }, /* Qc */
194 { md_cond_body, md_pre_raw, md_post_raw, "'`", "`'" }, /* Ql */
195 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qo */
196 { md_cond_body, md_pre_word, md_post_word, "\"", "\"" }, /* Qq */
197 { NULL, NULL, NULL, NULL, NULL }, /* Re */
198 { md_cond_body, md_pre_Rs, NULL, NULL, NULL }, /* Rs */
199 { NULL, NULL, NULL, NULL, NULL }, /* Sc */
200 { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* So */
201 { md_cond_body, md_pre_word, md_post_word, "'", "'" }, /* Sq */
202 { NULL, md_pre_Sm, NULL, NULL, NULL }, /* Sm */
203 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Sx */
204 { NULL, md_pre_raw, md_post_raw, "**", "**" }, /* Sy */
205 { NULL, md_pre_raw, md_post_raw, "`", "`" }, /* Tn */
206 { NULL, NULL, NULL, NULL, NULL }, /* Ux */
207 { NULL, NULL, NULL, NULL, NULL }, /* Xc */
208 { NULL, NULL, NULL, NULL, NULL }, /* Xo */
209 { NULL, md_pre_Fo, md_post_Fo, "**", "**" }, /* Fo */
210 { NULL, NULL, NULL, NULL, NULL }, /* Fc */
211 { md_cond_body, md_pre_word, md_post_word, "[", "]" }, /* Oo */
212 { NULL, NULL, NULL, NULL, NULL }, /* Oc */
213 { NULL, md_pre_Bk, md_post_Bk, NULL, NULL }, /* Bk */
214 { NULL, NULL, NULL, NULL, NULL }, /* Ek */
215 { NULL, NULL, NULL, NULL, NULL }, /* Bt */
216 { NULL, NULL, NULL, NULL, NULL }, /* Hf */
217 { NULL, md_pre_raw, md_post_raw, "*", "*" }, /* Fr */
218 { NULL, NULL, NULL, NULL, NULL }, /* Ud */
219 { NULL, NULL, md_post_Lb, NULL, NULL }, /* Lb */
220 { NULL, md_pre_abort, NULL, NULL, NULL }, /* Lp */
221 { NULL, md_pre_Lk, NULL, NULL, NULL }, /* Lk */
222 { NULL, md_pre_Mt, NULL, NULL, NULL }, /* Mt */
223 { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Brq */
224 { md_cond_body, md_pre_word, md_post_word, "{", "}" }, /* Bro */
225 { NULL, NULL, NULL, NULL, NULL }, /* Brc */
226 { NULL, NULL, md_post_pc, NULL, NULL }, /* %C */
227 { NULL, md_pre_skip, NULL, NULL, NULL }, /* Es */
228 { md_cond_body, md_pre_En, md_post_En, NULL, NULL }, /* En */
229 { NULL, NULL, NULL, NULL, NULL }, /* Dx */
230 { NULL, NULL, md_post_pc, NULL, NULL }, /* %Q */
231 { NULL, md_pre_Lk, md_post_pc, NULL, NULL }, /* %U */
232 { NULL, NULL, NULL, NULL, NULL }, /* Ta */
233 { NULL, md_pre_skip, NULL, NULL, NULL }, /* Tg */
235 static const struct md_act *md_act(enum roff_tok);
237 static int outflags;
238 #define MD_spc (1 << 0) /* Blank character before next word. */
239 #define MD_spc_force (1 << 1) /* Even before trailing punctuation. */
240 #define MD_nonl (1 << 2) /* Prevent linebreak in markdown code. */
241 #define MD_nl (1 << 3) /* Break markdown code line. */
242 #define MD_br (1 << 4) /* Insert an output line break. */
243 #define MD_sp (1 << 5) /* Insert a paragraph break. */
244 #define MD_Sm (1 << 6) /* Horizontal spacing mode. */
245 #define MD_Bk (1 << 7) /* Word keep mode. */
246 #define MD_An_split (1 << 8) /* Author mode is "split". */
247 #define MD_An_nosplit (1 << 9) /* Author mode is "nosplit". */
249 static int escflags; /* Escape in generated markdown code: */
250 #define ESC_BOL (1 << 0) /* "#*+-" near the beginning of a line. */
251 #define ESC_NUM (1 << 1) /* "." after a leading number. */
252 #define ESC_HYP (1 << 2) /* "(" immediately after "]". */
253 #define ESC_SQU (1 << 4) /* "]" when "[" is open. */
254 #define ESC_FON (1 << 5) /* "*" immediately after unrelated "*". */
255 #define ESC_EOL (1 << 6) /* " " at the and of a line. */
257 static int code_blocks, quote_blocks, list_blocks;
258 static int outcount;
261 static const struct md_act *
262 md_act(enum roff_tok tok)
264 assert(tok >= MDOC_Dd && tok <= MDOC_MAX);
265 return md_acts + (tok - MDOC_Dd);
268 void
269 markdown_mdoc(void *arg, const struct roff_meta *mdoc)
271 outflags = MD_Sm;
272 md_word(mdoc->title);
273 if (mdoc->msec != NULL) {
274 outflags &= ~MD_spc;
275 md_word("(");
276 md_word(mdoc->msec);
277 md_word(")");
279 md_word("-");
280 md_word(mdoc->vol);
281 if (mdoc->arch != NULL) {
282 md_word("(");
283 md_word(mdoc->arch);
284 md_word(")");
286 outflags |= MD_sp;
288 md_nodelist(mdoc->first->child);
290 outflags |= MD_sp;
291 md_word(mdoc->os);
292 md_word("-");
293 md_word(mdoc->date);
294 putchar('\n');
297 static void
298 md_nodelist(struct roff_node *n)
300 while (n != NULL) {
301 md_node(n);
302 n = n->next;
306 static void
307 md_node(struct roff_node *n)
309 const struct md_act *act;
310 int cond, process_children;
312 if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
313 return;
315 if (outflags & MD_nonl)
316 outflags &= ~(MD_nl | MD_sp);
317 else if (outflags & MD_spc &&
318 n->flags & NODE_LINE &&
319 !roff_node_transparent(n))
320 outflags |= MD_nl;
322 act = NULL;
323 cond = 0;
324 process_children = 1;
325 n->flags &= ~NODE_ENDED;
327 if (n->type == ROFFT_TEXT) {
328 if (n->flags & NODE_DELIMC)
329 outflags &= ~(MD_spc | MD_spc_force);
330 else if (outflags & MD_Sm)
331 outflags |= MD_spc_force;
332 md_word(n->string);
333 if (n->flags & NODE_DELIMO)
334 outflags &= ~(MD_spc | MD_spc_force);
335 else if (outflags & MD_Sm)
336 outflags |= MD_spc;
337 } else if (n->tok < ROFF_MAX) {
338 switch (n->tok) {
339 case ROFF_br:
340 process_children = md_pre_br(n);
341 break;
342 case ROFF_sp:
343 process_children = md_pre_Pp(n);
344 break;
345 default:
346 process_children = 0;
347 break;
349 } else {
350 act = md_act(n->tok);
351 cond = act->cond == NULL || (*act->cond)(n);
352 if (cond && act->pre != NULL &&
353 (n->end == ENDBODY_NOT || n->child != NULL))
354 process_children = (*act->pre)(n);
357 if (process_children && n->child != NULL)
358 md_nodelist(n->child);
360 if (n->flags & NODE_ENDED)
361 return;
363 if (cond && act->post != NULL)
364 (*act->post)(n);
366 if (n->end != ENDBODY_NOT)
367 n->body->flags |= NODE_ENDED;
370 static const char *
371 md_stack(char c)
373 static char *stack;
374 static size_t sz;
375 static size_t cur;
377 switch (c) {
378 case '\0':
379 break;
380 case (char)-1:
381 assert(cur);
382 stack[--cur] = '\0';
383 break;
384 default:
385 if (cur + 1 >= sz) {
386 sz += 8;
387 stack = mandoc_realloc(stack, sz);
389 stack[cur] = c;
390 stack[++cur] = '\0';
391 break;
393 return stack == NULL ? "" : stack;
397 * Handle vertical and horizontal spacing.
399 static void
400 md_preword(void)
402 const char *cp;
405 * If a list block is nested inside a code block or a blockquote,
406 * blank lines for paragraph breaks no longer work; instead,
407 * they terminate the list. Work around this markdown issue
408 * by using mere line breaks instead.
411 if (list_blocks && outflags & MD_sp) {
412 outflags &= ~MD_sp;
413 outflags |= MD_br;
417 * End the old line if requested.
418 * Escape whitespace at the end of the markdown line
419 * such that it won't look like an output line break.
422 if (outflags & MD_sp)
423 putchar('\n');
424 else if (outflags & MD_br) {
425 putchar(' ');
426 putchar(' ');
427 } else if (outflags & MD_nl && escflags & ESC_EOL)
428 md_named("zwnj");
430 /* Start a new line if necessary. */
432 if (outflags & (MD_nl | MD_br | MD_sp)) {
433 putchar('\n');
434 for (cp = md_stack('\0'); *cp != '\0'; cp++) {
435 putchar(*cp);
436 if (*cp == '>')
437 putchar(' ');
439 outflags &= ~(MD_nl | MD_br | MD_sp);
440 escflags = ESC_BOL;
441 outcount = 0;
443 /* Handle horizontal spacing. */
445 } else if (outflags & MD_spc) {
446 if (outflags & MD_Bk)
447 fputs("&nbsp;", stdout);
448 else
449 putchar(' ');
450 escflags &= ~ESC_FON;
451 outcount++;
454 outflags &= ~(MD_spc_force | MD_nonl);
455 if (outflags & MD_Sm)
456 outflags |= MD_spc;
457 else
458 outflags &= ~MD_spc;
462 * Print markdown syntax elements.
463 * Can also be used for constant strings when neither escaping
464 * nor delimiter handling is required.
466 static void
467 md_rawword(const char *s)
469 md_preword();
471 if (*s == '\0')
472 return;
474 if (escflags & ESC_FON) {
475 escflags &= ~ESC_FON;
476 if (*s == '*' && !code_blocks)
477 fputs("&zwnj;", stdout);
480 while (*s != '\0') {
481 switch(*s) {
482 case '*':
483 if (s[1] == '\0')
484 escflags |= ESC_FON;
485 break;
486 case '[':
487 escflags |= ESC_SQU;
488 break;
489 case ']':
490 escflags |= ESC_HYP;
491 escflags &= ~ESC_SQU;
492 break;
493 default:
494 break;
496 md_char(*s++);
498 if (s[-1] == ' ')
499 escflags |= ESC_EOL;
500 else
501 escflags &= ~ESC_EOL;
505 * Print text and mdoc(7) syntax elements.
507 static void
508 md_word(const char *s)
510 const char *seq, *prevfont, *currfont, *nextfont;
511 char c;
512 int bs, sz, uc, breakline;
514 /* No spacing before closing delimiters. */
515 if (s[0] != '\0' && s[1] == '\0' &&
516 strchr("!),.:;?]", s[0]) != NULL &&
517 (outflags & MD_spc_force) == 0)
518 outflags &= ~MD_spc;
520 md_preword();
522 if (*s == '\0')
523 return;
525 /* No spacing after opening delimiters. */
526 if ((s[0] == '(' || s[0] == '[') && s[1] == '\0')
527 outflags &= ~MD_spc;
529 breakline = 0;
530 prevfont = currfont = "";
531 while ((c = *s++) != '\0') {
532 bs = 0;
533 switch(c) {
534 case ASCII_NBRSP:
535 if (code_blocks)
536 c = ' ';
537 else {
538 md_named("nbsp");
539 c = '\0';
541 break;
542 case ASCII_HYPH:
543 bs = escflags & ESC_BOL && !code_blocks;
544 c = '-';
545 break;
546 case ASCII_BREAK:
547 continue;
548 case '#':
549 case '+':
550 case '-':
551 bs = escflags & ESC_BOL && !code_blocks;
552 break;
553 case '(':
554 bs = escflags & ESC_HYP && !code_blocks;
555 break;
556 case ')':
557 bs = escflags & ESC_NUM && !code_blocks;
558 break;
559 case '*':
560 case '[':
561 case '_':
562 case '`':
563 bs = !code_blocks;
564 break;
565 case '.':
566 bs = escflags & ESC_NUM && !code_blocks;
567 break;
568 case '<':
569 if (code_blocks == 0) {
570 md_named("lt");
571 c = '\0';
573 break;
574 case '=':
575 if (escflags & ESC_BOL && !code_blocks) {
576 md_named("equals");
577 c = '\0';
579 break;
580 case '>':
581 if (code_blocks == 0) {
582 md_named("gt");
583 c = '\0';
585 break;
586 case '\\':
587 uc = 0;
588 nextfont = NULL;
589 switch (mandoc_escape(&s, &seq, &sz)) {
590 case ESCAPE_UNICODE:
591 uc = mchars_num2uc(seq + 1, sz - 1);
592 break;
593 case ESCAPE_NUMBERED:
594 uc = mchars_num2char(seq, sz);
595 break;
596 case ESCAPE_SPECIAL:
597 uc = mchars_spec2cp(seq, sz);
598 break;
599 case ESCAPE_UNDEF:
600 uc = *seq;
601 break;
602 case ESCAPE_DEVICE:
603 md_rawword("markdown");
604 continue;
605 case ESCAPE_FONTBOLD:
606 case ESCAPE_FONTCB:
607 nextfont = "**";
608 break;
609 case ESCAPE_FONTITALIC:
610 case ESCAPE_FONTCI:
611 nextfont = "*";
612 break;
613 case ESCAPE_FONTBI:
614 nextfont = "***";
615 break;
616 case ESCAPE_FONT:
617 case ESCAPE_FONTCR:
618 case ESCAPE_FONTROMAN:
619 nextfont = "";
620 break;
621 case ESCAPE_FONTPREV:
622 nextfont = prevfont;
623 break;
624 case ESCAPE_BREAK:
625 breakline = 1;
626 break;
627 case ESCAPE_NOSPACE:
628 case ESCAPE_SKIPCHAR:
629 case ESCAPE_OVERSTRIKE:
630 /* XXX not implemented */
631 /* FALLTHROUGH */
632 case ESCAPE_ERROR:
633 default:
634 break;
636 if (nextfont != NULL && !code_blocks) {
637 if (*currfont != '\0') {
638 outflags &= ~MD_spc;
639 md_rawword(currfont);
641 prevfont = currfont;
642 currfont = nextfont;
643 if (*currfont != '\0') {
644 outflags &= ~MD_spc;
645 md_rawword(currfont);
648 if (uc) {
649 if ((uc < 0x20 && uc != 0x09) ||
650 (uc > 0x7E && uc < 0xA0))
651 uc = 0xFFFD;
652 if (code_blocks) {
653 seq = mchars_uc2str(uc);
654 fputs(seq, stdout);
655 outcount += strlen(seq);
656 } else {
657 printf("&#%d;", uc);
658 outcount++;
660 escflags &= ~ESC_FON;
662 c = '\0';
663 break;
664 case ']':
665 bs = escflags & ESC_SQU && !code_blocks;
666 escflags |= ESC_HYP;
667 break;
668 default:
669 break;
671 if (bs)
672 putchar('\\');
673 md_char(c);
674 if (breakline &&
675 (*s == '\0' || *s == ' ' || *s == ASCII_NBRSP)) {
676 printf(" \n");
677 breakline = 0;
678 while (*s == ' ' || *s == ASCII_NBRSP)
679 s++;
682 if (*currfont != '\0') {
683 outflags &= ~MD_spc;
684 md_rawword(currfont);
685 } else if (s[-2] == ' ')
686 escflags |= ESC_EOL;
687 else
688 escflags &= ~ESC_EOL;
692 * Print a single HTML named character reference.
694 static void
695 md_named(const char *s)
697 printf("&%s;", s);
698 escflags &= ~(ESC_FON | ESC_EOL);
699 outcount++;
703 * Print a single raw character and maintain certain escape flags.
705 static void
706 md_char(unsigned char c)
708 if (c != '\0') {
709 putchar(c);
710 if (c == '*')
711 escflags |= ESC_FON;
712 else
713 escflags &= ~ESC_FON;
714 outcount++;
716 if (c != ']')
717 escflags &= ~ESC_HYP;
718 if (c == ' ' || c == '\t' || c == '>')
719 return;
720 if (isdigit(c) == 0)
721 escflags &= ~ESC_NUM;
722 else if (escflags & ESC_BOL)
723 escflags |= ESC_NUM;
724 escflags &= ~ESC_BOL;
727 static int
728 md_cond_head(struct roff_node *n)
730 return n->type == ROFFT_HEAD;
733 static int
734 md_cond_body(struct roff_node *n)
736 return n->type == ROFFT_BODY;
739 static int
740 md_pre_abort(struct roff_node *n)
742 abort();
745 static int
746 md_pre_raw(struct roff_node *n)
748 const char *prefix;
750 if ((prefix = md_act(n->tok)->prefix) != NULL) {
751 md_rawword(prefix);
752 outflags &= ~MD_spc;
753 if (*prefix == '`')
754 code_blocks++;
756 return 1;
759 static void
760 md_post_raw(struct roff_node *n)
762 const char *suffix;
764 if ((suffix = md_act(n->tok)->suffix) != NULL) {
765 outflags &= ~(MD_spc | MD_nl);
766 md_rawword(suffix);
767 if (*suffix == '`')
768 code_blocks--;
772 static int
773 md_pre_word(struct roff_node *n)
775 const char *prefix;
777 if ((prefix = md_act(n->tok)->prefix) != NULL) {
778 md_word(prefix);
779 outflags &= ~MD_spc;
781 return 1;
784 static void
785 md_post_word(struct roff_node *n)
787 const char *suffix;
789 if ((suffix = md_act(n->tok)->suffix) != NULL) {
790 outflags &= ~(MD_spc | MD_nl);
791 md_word(suffix);
795 static void
796 md_post_pc(struct roff_node *n)
798 struct roff_node *nn;
800 md_post_raw(n);
801 if (n->parent->tok != MDOC_Rs)
802 return;
804 if ((nn = roff_node_next(n)) != NULL) {
805 md_word(",");
806 if (nn->tok == n->tok &&
807 (nn = roff_node_prev(n)) != NULL &&
808 nn->tok == n->tok)
809 md_word("and");
810 } else {
811 md_word(".");
812 outflags |= MD_nl;
816 static int
817 md_pre_skip(struct roff_node *n)
819 return 0;
822 static void
823 md_pre_syn(struct roff_node *n)
825 struct roff_node *np;
827 if ((n->flags & NODE_SYNPRETTY) == 0 ||
828 (np = roff_node_prev(n)) == NULL)
829 return;
831 if (np->tok == n->tok &&
832 n->tok != MDOC_Ft &&
833 n->tok != MDOC_Fo &&
834 n->tok != MDOC_Fn) {
835 outflags |= MD_br;
836 return;
839 switch (np->tok) {
840 case MDOC_Fd:
841 case MDOC_Fn:
842 case MDOC_Fo:
843 case MDOC_In:
844 case MDOC_Vt:
845 outflags |= MD_sp;
846 break;
847 case MDOC_Ft:
848 if (n->tok != MDOC_Fn && n->tok != MDOC_Fo) {
849 outflags |= MD_sp;
850 break;
852 /* FALLTHROUGH */
853 default:
854 outflags |= MD_br;
855 break;
859 static int
860 md_pre_An(struct roff_node *n)
862 switch (n->norm->An.auth) {
863 case AUTH_split:
864 outflags &= ~MD_An_nosplit;
865 outflags |= MD_An_split;
866 return 0;
867 case AUTH_nosplit:
868 outflags &= ~MD_An_split;
869 outflags |= MD_An_nosplit;
870 return 0;
871 default:
872 if (outflags & MD_An_split)
873 outflags |= MD_br;
874 else if (n->sec == SEC_AUTHORS &&
875 ! (outflags & MD_An_nosplit))
876 outflags |= MD_An_split;
877 return 1;
881 static int
882 md_pre_Ap(struct roff_node *n)
884 outflags &= ~MD_spc;
885 md_word("'");
886 outflags &= ~MD_spc;
887 return 0;
890 static int
891 md_pre_Bd(struct roff_node *n)
893 switch (n->norm->Bd.type) {
894 case DISP_unfilled:
895 case DISP_literal:
896 return md_pre_Dl(n);
897 default:
898 return md_pre_D1(n);
902 static int
903 md_pre_Bk(struct roff_node *n)
905 switch (n->type) {
906 case ROFFT_BLOCK:
907 return 1;
908 case ROFFT_BODY:
909 outflags |= MD_Bk;
910 return 1;
911 default:
912 return 0;
916 static void
917 md_post_Bk(struct roff_node *n)
919 if (n->type == ROFFT_BODY)
920 outflags &= ~MD_Bk;
923 static int
924 md_pre_Bl(struct roff_node *n)
926 n->norm->Bl.count = 0;
927 if (n->norm->Bl.type == LIST_column)
928 md_pre_Dl(n);
929 outflags |= MD_sp;
930 return 1;
933 static void
934 md_post_Bl(struct roff_node *n)
936 n->norm->Bl.count = 0;
937 if (n->norm->Bl.type == LIST_column)
938 md_post_D1(n);
939 outflags |= MD_sp;
942 static int
943 md_pre_D1(struct roff_node *n)
946 * Markdown blockquote syntax does not work inside code blocks.
947 * The best we can do is fall back to another nested code block.
949 if (code_blocks) {
950 md_stack('\t');
951 code_blocks++;
952 } else {
953 md_stack('>');
954 quote_blocks++;
956 outflags |= MD_sp;
957 return 1;
960 static void
961 md_post_D1(struct roff_node *n)
963 md_stack((char)-1);
964 if (code_blocks)
965 code_blocks--;
966 else
967 quote_blocks--;
968 outflags |= MD_sp;
971 static int
972 md_pre_Dl(struct roff_node *n)
975 * Markdown code block syntax does not work inside blockquotes.
976 * The best we can do is fall back to another nested blockquote.
978 if (quote_blocks) {
979 md_stack('>');
980 quote_blocks++;
981 } else {
982 md_stack('\t');
983 code_blocks++;
985 outflags |= MD_sp;
986 return 1;
989 static int
990 md_pre_En(struct roff_node *n)
992 if (n->norm->Es == NULL ||
993 n->norm->Es->child == NULL)
994 return 1;
996 md_word(n->norm->Es->child->string);
997 outflags &= ~MD_spc;
998 return 1;
1001 static void
1002 md_post_En(struct roff_node *n)
1004 if (n->norm->Es == NULL ||
1005 n->norm->Es->child == NULL ||
1006 n->norm->Es->child->next == NULL)
1007 return;
1009 outflags &= ~MD_spc;
1010 md_word(n->norm->Es->child->next->string);
1013 static int
1014 md_pre_Eo(struct roff_node *n)
1016 if (n->end == ENDBODY_NOT &&
1017 n->parent->head->child == NULL &&
1018 n->child != NULL &&
1019 n->child->end != ENDBODY_NOT)
1020 md_preword();
1021 else if (n->end != ENDBODY_NOT ? n->child != NULL :
1022 n->parent->head->child != NULL && (n->child != NULL ||
1023 (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1024 outflags &= ~(MD_spc | MD_nl);
1025 return 1;
1028 static void
1029 md_post_Eo(struct roff_node *n)
1031 if (n->end != ENDBODY_NOT) {
1032 outflags |= MD_spc;
1033 return;
1036 if (n->child == NULL && n->parent->head->child == NULL)
1037 return;
1039 if (n->parent->tail != NULL && n->parent->tail->child != NULL)
1040 outflags &= ~MD_spc;
1041 else
1042 outflags |= MD_spc;
1045 static int
1046 md_pre_Fa(struct roff_node *n)
1048 int am_Fa;
1050 am_Fa = n->tok == MDOC_Fa;
1052 if (am_Fa)
1053 n = n->child;
1055 while (n != NULL) {
1056 md_rawword("*");
1057 outflags &= ~MD_spc;
1058 md_node(n);
1059 outflags &= ~MD_spc;
1060 md_rawword("*");
1061 if ((n = n->next) != NULL)
1062 md_word(",");
1064 return 0;
1067 static void
1068 md_post_Fa(struct roff_node *n)
1070 struct roff_node *nn;
1072 if ((nn = roff_node_next(n)) != NULL && nn->tok == MDOC_Fa)
1073 md_word(",");
1076 static int
1077 md_pre_Fd(struct roff_node *n)
1079 md_pre_syn(n);
1080 md_pre_raw(n);
1081 return 1;
1084 static void
1085 md_post_Fd(struct roff_node *n)
1087 md_post_raw(n);
1088 outflags |= MD_br;
1091 static void
1092 md_post_Fl(struct roff_node *n)
1094 struct roff_node *nn;
1096 md_post_raw(n);
1097 if (n->child == NULL && (nn = roff_node_next(n)) != NULL &&
1098 nn->type != ROFFT_TEXT && (nn->flags & NODE_LINE) == 0)
1099 outflags &= ~MD_spc;
1102 static int
1103 md_pre_Fn(struct roff_node *n)
1105 md_pre_syn(n);
1107 if ((n = n->child) == NULL)
1108 return 0;
1110 md_rawword("**");
1111 outflags &= ~MD_spc;
1112 md_node(n);
1113 outflags &= ~MD_spc;
1114 md_rawword("**");
1115 outflags &= ~MD_spc;
1116 md_word("(");
1118 if ((n = n->next) != NULL)
1119 md_pre_Fa(n);
1120 return 0;
1123 static void
1124 md_post_Fn(struct roff_node *n)
1126 md_word(")");
1127 if (n->flags & NODE_SYNPRETTY) {
1128 md_word(";");
1129 outflags |= MD_sp;
1133 static int
1134 md_pre_Fo(struct roff_node *n)
1136 switch (n->type) {
1137 case ROFFT_BLOCK:
1138 md_pre_syn(n);
1139 break;
1140 case ROFFT_HEAD:
1141 if (n->child == NULL)
1142 return 0;
1143 md_pre_raw(n);
1144 break;
1145 case ROFFT_BODY:
1146 outflags &= ~(MD_spc | MD_nl);
1147 md_word("(");
1148 break;
1149 default:
1150 break;
1152 return 1;
1155 static void
1156 md_post_Fo(struct roff_node *n)
1158 switch (n->type) {
1159 case ROFFT_HEAD:
1160 if (n->child != NULL)
1161 md_post_raw(n);
1162 break;
1163 case ROFFT_BODY:
1164 md_post_Fn(n);
1165 break;
1166 default:
1167 break;
1171 static int
1172 md_pre_In(struct roff_node *n)
1174 if (n->flags & NODE_SYNPRETTY) {
1175 md_pre_syn(n);
1176 md_rawword("**");
1177 outflags &= ~MD_spc;
1178 md_word("#include <");
1179 } else {
1180 md_word("<");
1181 outflags &= ~MD_spc;
1182 md_rawword("*");
1184 outflags &= ~MD_spc;
1185 return 1;
1188 static void
1189 md_post_In(struct roff_node *n)
1191 if (n->flags & NODE_SYNPRETTY) {
1192 outflags &= ~MD_spc;
1193 md_rawword(">**");
1194 outflags |= MD_nl;
1195 } else {
1196 outflags &= ~MD_spc;
1197 md_rawword("*>");
1201 static int
1202 md_pre_It(struct roff_node *n)
1204 struct roff_node *bln;
1206 switch (n->type) {
1207 case ROFFT_BLOCK:
1208 return 1;
1210 case ROFFT_HEAD:
1211 bln = n->parent->parent;
1212 if (bln->norm->Bl.comp == 0 &&
1213 bln->norm->Bl.type != LIST_column)
1214 outflags |= MD_sp;
1215 outflags |= MD_nl;
1217 switch (bln->norm->Bl.type) {
1218 case LIST_item:
1219 outflags |= MD_br;
1220 return 0;
1221 case LIST_inset:
1222 case LIST_diag:
1223 case LIST_ohang:
1224 outflags |= MD_br;
1225 return 1;
1226 case LIST_tag:
1227 case LIST_hang:
1228 outflags |= MD_sp;
1229 return 1;
1230 case LIST_bullet:
1231 md_rawword("*\t");
1232 break;
1233 case LIST_dash:
1234 case LIST_hyphen:
1235 md_rawword("-\t");
1236 break;
1237 case LIST_enum:
1238 md_preword();
1239 if (bln->norm->Bl.count < 99)
1240 bln->norm->Bl.count++;
1241 printf("%d.\t", bln->norm->Bl.count);
1242 escflags &= ~ESC_FON;
1243 break;
1244 case LIST_column:
1245 outflags |= MD_br;
1246 return 0;
1247 default:
1248 return 0;
1250 outflags &= ~MD_spc;
1251 outflags |= MD_nonl;
1252 outcount = 0;
1253 md_stack('\t');
1254 if (code_blocks || quote_blocks)
1255 list_blocks++;
1256 return 0;
1258 case ROFFT_BODY:
1259 bln = n->parent->parent;
1260 switch (bln->norm->Bl.type) {
1261 case LIST_ohang:
1262 outflags |= MD_br;
1263 break;
1264 case LIST_tag:
1265 case LIST_hang:
1266 md_pre_D1(n);
1267 break;
1268 default:
1269 break;
1271 return 1;
1273 default:
1274 return 0;
1278 static void
1279 md_post_It(struct roff_node *n)
1281 struct roff_node *bln;
1282 int i, nc;
1284 if (n->type != ROFFT_BODY)
1285 return;
1287 bln = n->parent->parent;
1288 switch (bln->norm->Bl.type) {
1289 case LIST_bullet:
1290 case LIST_dash:
1291 case LIST_hyphen:
1292 case LIST_enum:
1293 md_stack((char)-1);
1294 if (code_blocks || quote_blocks)
1295 list_blocks--;
1296 break;
1297 case LIST_tag:
1298 case LIST_hang:
1299 md_post_D1(n);
1300 break;
1302 case LIST_column:
1303 if (n->next == NULL)
1304 break;
1306 /* Calculate the array index of the current column. */
1308 i = 0;
1309 while ((n = n->prev) != NULL && n->type != ROFFT_HEAD)
1310 i++;
1313 * If a width was specified for this column,
1314 * subtract what printed, and
1315 * add the same spacing as in mdoc_term.c.
1318 nc = bln->norm->Bl.ncols;
1319 i = i < nc ? strlen(bln->norm->Bl.cols[i]) - outcount +
1320 (nc < 5 ? 4 : nc == 5 ? 3 : 1) : 1;
1321 if (i < 1)
1322 i = 1;
1323 while (i-- > 0)
1324 putchar(' ');
1326 outflags &= ~MD_spc;
1327 escflags &= ~ESC_FON;
1328 outcount = 0;
1329 break;
1331 default:
1332 break;
1336 static void
1337 md_post_Lb(struct roff_node *n)
1339 if (n->sec == SEC_LIBRARY)
1340 outflags |= MD_br;
1343 static void
1344 md_uri(const char *s)
1346 while (*s != '\0') {
1347 if (strchr("%()<>", *s) != NULL) {
1348 printf("%%%2.2hhX", *s);
1349 outcount += 3;
1350 } else {
1351 putchar(*s);
1352 outcount++;
1354 s++;
1358 static int
1359 md_pre_Lk(struct roff_node *n)
1361 const struct roff_node *link, *descr, *punct;
1363 if ((link = n->child) == NULL)
1364 return 0;
1366 /* Find beginning of trailing punctuation. */
1367 punct = n->last;
1368 while (punct != link && punct->flags & NODE_DELIMC)
1369 punct = punct->prev;
1370 punct = punct->next;
1372 /* Link text. */
1373 descr = link->next;
1374 if (descr == punct)
1375 descr = link; /* no text */
1376 md_rawword("[");
1377 outflags &= ~MD_spc;
1378 do {
1379 md_word(descr->string);
1380 descr = descr->next;
1381 } while (descr != punct);
1382 outflags &= ~MD_spc;
1384 /* Link target. */
1385 md_rawword("](");
1386 md_uri(link->string);
1387 outflags &= ~MD_spc;
1388 md_rawword(")");
1390 /* Trailing punctuation. */
1391 while (punct != NULL) {
1392 md_word(punct->string);
1393 punct = punct->next;
1395 return 0;
1398 static int
1399 md_pre_Mt(struct roff_node *n)
1401 const struct roff_node *nch;
1403 md_rawword("[");
1404 outflags &= ~MD_spc;
1405 for (nch = n->child; nch != NULL; nch = nch->next)
1406 md_word(nch->string);
1407 outflags &= ~MD_spc;
1408 md_rawword("](mailto:");
1409 for (nch = n->child; nch != NULL; nch = nch->next) {
1410 md_uri(nch->string);
1411 if (nch->next != NULL) {
1412 putchar(' ');
1413 outcount++;
1416 outflags &= ~MD_spc;
1417 md_rawword(")");
1418 return 0;
1421 static int
1422 md_pre_Nd(struct roff_node *n)
1424 outflags &= ~MD_nl;
1425 outflags |= MD_spc;
1426 md_word("-");
1427 return 1;
1430 static int
1431 md_pre_Nm(struct roff_node *n)
1433 switch (n->type) {
1434 case ROFFT_BLOCK:
1435 outflags |= MD_Bk;
1436 md_pre_syn(n);
1437 break;
1438 case ROFFT_HEAD:
1439 case ROFFT_ELEM:
1440 md_pre_raw(n);
1441 break;
1442 default:
1443 break;
1445 return 1;
1448 static void
1449 md_post_Nm(struct roff_node *n)
1451 switch (n->type) {
1452 case ROFFT_BLOCK:
1453 outflags &= ~MD_Bk;
1454 break;
1455 case ROFFT_HEAD:
1456 case ROFFT_ELEM:
1457 md_post_raw(n);
1458 break;
1459 default:
1460 break;
1464 static int
1465 md_pre_No(struct roff_node *n)
1467 outflags |= MD_spc_force;
1468 return 1;
1471 static int
1472 md_pre_Ns(struct roff_node *n)
1474 outflags &= ~MD_spc;
1475 return 0;
1478 static void
1479 md_post_Pf(struct roff_node *n)
1481 if (n->next != NULL && (n->next->flags & NODE_LINE) == 0)
1482 outflags &= ~MD_spc;
1485 static int
1486 md_pre_Pp(struct roff_node *n)
1488 outflags |= MD_sp;
1489 return 0;
1492 static int
1493 md_pre_Rs(struct roff_node *n)
1495 if (n->sec == SEC_SEE_ALSO)
1496 outflags |= MD_sp;
1497 return 1;
1500 static int
1501 md_pre_Sh(struct roff_node *n)
1503 switch (n->type) {
1504 case ROFFT_BLOCK:
1505 if (n->sec == SEC_AUTHORS)
1506 outflags &= ~(MD_An_split | MD_An_nosplit);
1507 break;
1508 case ROFFT_HEAD:
1509 outflags |= MD_sp;
1510 md_rawword(n->tok == MDOC_Sh ? "#" : "##");
1511 break;
1512 case ROFFT_BODY:
1513 outflags |= MD_sp;
1514 break;
1515 default:
1516 break;
1518 return 1;
1521 static int
1522 md_pre_Sm(struct roff_node *n)
1524 if (n->child == NULL)
1525 outflags ^= MD_Sm;
1526 else if (strcmp("on", n->child->string) == 0)
1527 outflags |= MD_Sm;
1528 else
1529 outflags &= ~MD_Sm;
1531 if (outflags & MD_Sm)
1532 outflags |= MD_spc;
1534 return 0;
1537 static int
1538 md_pre_Vt(struct roff_node *n)
1540 switch (n->type) {
1541 case ROFFT_BLOCK:
1542 md_pre_syn(n);
1543 return 1;
1544 case ROFFT_BODY:
1545 case ROFFT_ELEM:
1546 md_pre_raw(n);
1547 return 1;
1548 default:
1549 return 0;
1553 static void
1554 md_post_Vt(struct roff_node *n)
1556 switch (n->type) {
1557 case ROFFT_BODY:
1558 case ROFFT_ELEM:
1559 md_post_raw(n);
1560 break;
1561 default:
1562 break;
1566 static int
1567 md_pre_Xr(struct roff_node *n)
1569 n = n->child;
1570 if (n == NULL)
1571 return 0;
1572 md_node(n);
1573 n = n->next;
1574 if (n == NULL)
1575 return 0;
1576 outflags &= ~MD_spc;
1577 md_word("(");
1578 md_node(n);
1579 md_word(")");
1580 return 0;
1583 static int
1584 md_pre__T(struct roff_node *n)
1586 if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1587 md_word("\"");
1588 else
1589 md_rawword("*");
1590 outflags &= ~MD_spc;
1591 return 1;
1594 static void
1595 md_post__T(struct roff_node *n)
1597 outflags &= ~MD_spc;
1598 if (n->parent->tok == MDOC_Rs && n->parent->norm->Rs.quote_T)
1599 md_word("\"");
1600 else
1601 md_rawword("*");
1602 md_post_pc(n);
1605 static int
1606 md_pre_br(struct roff_node *n)
1608 outflags |= MD_br;
1609 return 0;