]> git.cameronkatri.com Git - mandoc.git/blob - html.c
bugfix: .Tg must be ignored completely in these output modes
[mandoc.git] / html.c
1 /* $Id: html.c,v 1.262 2020/01/19 18:02:00 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011-2015, 2017-2019 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include "config.h"
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <stdarg.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "mandoc_aux.h"
34 #include "mandoc_ohash.h"
35 #include "mandoc.h"
36 #include "roff.h"
37 #include "out.h"
38 #include "html.h"
39 #include "manconf.h"
40 #include "main.h"
41
42 struct htmldata {
43 const char *name;
44 int flags;
45 #define HTML_INPHRASE (1 << 0) /* Can appear in phrasing context. */
46 #define HTML_TOPHRASE (1 << 1) /* Establishes phrasing context. */
47 #define HTML_NOSTACK (1 << 2) /* Does not have an end tag. */
48 #define HTML_NLBEFORE (1 << 3) /* Output line break before opening. */
49 #define HTML_NLBEGIN (1 << 4) /* Output line break after opening. */
50 #define HTML_NLEND (1 << 5) /* Output line break before closing. */
51 #define HTML_NLAFTER (1 << 6) /* Output line break after closing. */
52 #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER)
53 #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND)
54 #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE)
55 #define HTML_INDENT (1 << 7) /* Indent content by two spaces. */
56 #define HTML_NOINDENT (1 << 8) /* Exception: never indent content. */
57 };
58
59 static const struct htmldata htmltags[TAG_MAX] = {
60 {"html", HTML_NLALL},
61 {"head", HTML_NLALL | HTML_INDENT},
62 {"meta", HTML_NOSTACK | HTML_NLALL},
63 {"link", HTML_NOSTACK | HTML_NLALL},
64 {"style", HTML_NLALL | HTML_INDENT},
65 {"title", HTML_NLAROUND},
66 {"body", HTML_NLALL},
67 {"div", HTML_NLAROUND},
68 {"section", HTML_NLALL},
69 {"table", HTML_NLALL | HTML_INDENT},
70 {"tr", HTML_NLALL | HTML_INDENT},
71 {"td", HTML_NLAROUND},
72 {"li", HTML_NLAROUND | HTML_INDENT},
73 {"ul", HTML_NLALL | HTML_INDENT},
74 {"ol", HTML_NLALL | HTML_INDENT},
75 {"dl", HTML_NLALL | HTML_INDENT},
76 {"dt", HTML_NLAROUND},
77 {"dd", HTML_NLAROUND | HTML_INDENT},
78 {"h1", HTML_TOPHRASE | HTML_NLAROUND},
79 {"h2", HTML_TOPHRASE | HTML_NLAROUND},
80 {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT},
81 {"pre", HTML_TOPHRASE | HTML_NLALL | HTML_NOINDENT},
82 {"a", HTML_INPHRASE | HTML_TOPHRASE},
83 {"b", HTML_INPHRASE | HTML_TOPHRASE},
84 {"cite", HTML_INPHRASE | HTML_TOPHRASE},
85 {"code", HTML_INPHRASE | HTML_TOPHRASE},
86 {"i", HTML_INPHRASE | HTML_TOPHRASE},
87 {"small", HTML_INPHRASE | HTML_TOPHRASE},
88 {"span", HTML_INPHRASE | HTML_TOPHRASE},
89 {"var", HTML_INPHRASE | HTML_TOPHRASE},
90 {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL},
91 {"mark", HTML_INPHRASE | HTML_NOSTACK },
92 {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT},
93 {"mrow", 0},
94 {"mi", 0},
95 {"mn", 0},
96 {"mo", 0},
97 {"msup", 0},
98 {"msub", 0},
99 {"msubsup", 0},
100 {"mfrac", 0},
101 {"msqrt", 0},
102 {"mfenced", 0},
103 {"mtable", 0},
104 {"mtr", 0},
105 {"mtd", 0},
106 {"munderover", 0},
107 {"munder", 0},
108 {"mover", 0},
109 };
110
111 /* Avoid duplicate HTML id= attributes. */
112 static struct ohash id_unique;
113
114 static void html_reset_internal(struct html *);
115 static void print_byte(struct html *, char);
116 static void print_endword(struct html *);
117 static void print_indent(struct html *);
118 static void print_word(struct html *, const char *);
119
120 static void print_ctag(struct html *, struct tag *);
121 static int print_escape(struct html *, char);
122 static int print_encode(struct html *, const char *, const char *, int);
123 static void print_href(struct html *, const char *, const char *, int);
124 static void print_metaf(struct html *);
125
126
127 void *
128 html_alloc(const struct manoutput *outopts)
129 {
130 struct html *h;
131
132 h = mandoc_calloc(1, sizeof(struct html));
133
134 h->tag = NULL;
135 h->style = outopts->style;
136 if ((h->base_man1 = outopts->man) == NULL)
137 h->base_man2 = NULL;
138 else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL)
139 *h->base_man2++ = '\0';
140 h->base_includes = outopts->includes;
141 if (outopts->fragment)
142 h->oflags |= HTML_FRAGMENT;
143 if (outopts->toc)
144 h->oflags |= HTML_TOC;
145
146 mandoc_ohash_init(&id_unique, 4, 0);
147
148 return h;
149 }
150
151 static void
152 html_reset_internal(struct html *h)
153 {
154 struct tag *tag;
155 char *cp;
156 unsigned int slot;
157
158 while ((tag = h->tag) != NULL) {
159 h->tag = tag->next;
160 free(tag);
161 }
162 cp = ohash_first(&id_unique, &slot);
163 while (cp != NULL) {
164 free(cp);
165 cp = ohash_next(&id_unique, &slot);
166 }
167 ohash_delete(&id_unique);
168 }
169
170 void
171 html_reset(void *p)
172 {
173 html_reset_internal(p);
174 mandoc_ohash_init(&id_unique, 4, 0);
175 }
176
177 void
178 html_free(void *p)
179 {
180 html_reset_internal(p);
181 free(p);
182 }
183
184 void
185 print_gen_head(struct html *h)
186 {
187 struct tag *t;
188
189 print_otag(h, TAG_META, "?", "charset", "utf-8");
190 if (h->style != NULL) {
191 print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
192 h->style, "type", "text/css", "media", "all");
193 return;
194 }
195
196 /*
197 * Print a minimal embedded style sheet.
198 */
199
200 t = print_otag(h, TAG_STYLE, "");
201 print_text(h, "table.head, table.foot { width: 100%; }");
202 print_endline(h);
203 print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
204 print_endline(h);
205 print_text(h, "td.head-vol { text-align: center; }");
206 print_endline(h);
207 print_text(h, ".Nd, .Bf, .Op { display: inline; }");
208 print_endline(h);
209 print_text(h, ".Pa, .Ad { font-style: italic; }");
210 print_endline(h);
211 print_text(h, ".Ms { font-weight: bold; }");
212 print_endline(h);
213 print_text(h, ".Bl-diag ");
214 print_byte(h, '>');
215 print_text(h, " dt { font-weight: bold; }");
216 print_endline(h);
217 print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd "
218 "{ font-weight: bold; font-family: inherit; }");
219 print_tagq(h, t);
220 }
221
222 int
223 html_setfont(struct html *h, enum mandoc_esc font)
224 {
225 switch (font) {
226 case ESCAPE_FONTPREV:
227 font = h->metal;
228 break;
229 case ESCAPE_FONTITALIC:
230 case ESCAPE_FONTBOLD:
231 case ESCAPE_FONTBI:
232 case ESCAPE_FONTCW:
233 case ESCAPE_FONTROMAN:
234 break;
235 case ESCAPE_FONT:
236 font = ESCAPE_FONTROMAN;
237 break;
238 default:
239 return 0;
240 }
241 h->metal = h->metac;
242 h->metac = font;
243 return 1;
244 }
245
246 static void
247 print_metaf(struct html *h)
248 {
249 if (h->metaf) {
250 print_tagq(h, h->metaf);
251 h->metaf = NULL;
252 }
253 switch (h->metac) {
254 case ESCAPE_FONTITALIC:
255 h->metaf = print_otag(h, TAG_I, "");
256 break;
257 case ESCAPE_FONTBOLD:
258 h->metaf = print_otag(h, TAG_B, "");
259 break;
260 case ESCAPE_FONTBI:
261 h->metaf = print_otag(h, TAG_B, "");
262 print_otag(h, TAG_I, "");
263 break;
264 case ESCAPE_FONTCW:
265 h->metaf = print_otag(h, TAG_SPAN, "c", "Li");
266 break;
267 default:
268 break;
269 }
270 }
271
272 void
273 html_close_paragraph(struct html *h)
274 {
275 struct tag *this, *next;
276 int flags;
277
278 this = h->tag;
279 for (;;) {
280 next = this->next;
281 flags = htmltags[this->tag].flags;
282 if (flags & (HTML_INPHRASE | HTML_TOPHRASE))
283 print_ctag(h, this);
284 if ((flags & HTML_INPHRASE) == 0)
285 break;
286 this = next;
287 }
288 }
289
290 /*
291 * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode.
292 * TOKEN_NONE does not switch. The old mode is returned.
293 */
294 enum roff_tok
295 html_fillmode(struct html *h, enum roff_tok want)
296 {
297 struct tag *t;
298 enum roff_tok had;
299
300 for (t = h->tag; t != NULL; t = t->next)
301 if (t->tag == TAG_PRE)
302 break;
303
304 had = t == NULL ? ROFF_fi : ROFF_nf;
305
306 if (want != had) {
307 switch (want) {
308 case ROFF_fi:
309 print_tagq(h, t);
310 break;
311 case ROFF_nf:
312 html_close_paragraph(h);
313 print_otag(h, TAG_PRE, "");
314 break;
315 case TOKEN_NONE:
316 break;
317 default:
318 abort();
319 }
320 }
321 return had;
322 }
323
324 char *
325 html_make_id(const struct roff_node *n, int unique)
326 {
327 const struct roff_node *nch;
328 char *buf, *bufs, *cp;
329 unsigned int slot;
330 int suffix;
331
332 for (nch = n->child; nch != NULL; nch = nch->next)
333 if (nch->type != ROFFT_TEXT)
334 return NULL;
335
336 buf = NULL;
337 deroff(&buf, n);
338 if (buf == NULL)
339 return NULL;
340
341 /*
342 * In ID attributes, only use ASCII characters that are
343 * permitted in URL-fragment strings according to the
344 * explicit list at:
345 * https://url.spec.whatwg.org/#url-fragment-string
346 */
347
348 for (cp = buf; *cp != '\0'; cp++)
349 if (isalnum((unsigned char)*cp) == 0 &&
350 strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL)
351 *cp = '_';
352
353 if (unique == 0)
354 return buf;
355
356 /* Avoid duplicate HTML id= attributes. */
357
358 bufs = NULL;
359 suffix = 1;
360 slot = ohash_qlookup(&id_unique, buf);
361 cp = ohash_find(&id_unique, slot);
362 if (cp != NULL) {
363 while (cp != NULL) {
364 free(bufs);
365 if (++suffix > 127) {
366 free(buf);
367 return NULL;
368 }
369 mandoc_asprintf(&bufs, "%s_%d", buf, suffix);
370 slot = ohash_qlookup(&id_unique, bufs);
371 cp = ohash_find(&id_unique, slot);
372 }
373 free(buf);
374 buf = bufs;
375 }
376 ohash_insert(&id_unique, slot, buf);
377 return buf;
378 }
379
380 static int
381 print_escape(struct html *h, char c)
382 {
383
384 switch (c) {
385 case '<':
386 print_word(h, "&lt;");
387 break;
388 case '>':
389 print_word(h, "&gt;");
390 break;
391 case '&':
392 print_word(h, "&amp;");
393 break;
394 case '"':
395 print_word(h, "&quot;");
396 break;
397 case ASCII_NBRSP:
398 print_word(h, "&nbsp;");
399 break;
400 case ASCII_HYPH:
401 print_byte(h, '-');
402 break;
403 case ASCII_BREAK:
404 break;
405 default:
406 return 0;
407 }
408 return 1;
409 }
410
411 static int
412 print_encode(struct html *h, const char *p, const char *pend, int norecurse)
413 {
414 char numbuf[16];
415 const char *seq;
416 size_t sz;
417 int c, len, breakline, nospace;
418 enum mandoc_esc esc;
419 static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
420 ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
421
422 if (pend == NULL)
423 pend = strchr(p, '\0');
424
425 breakline = 0;
426 nospace = 0;
427
428 while (p < pend) {
429 if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
430 h->flags &= ~HTML_SKIPCHAR;
431 p++;
432 continue;
433 }
434
435 for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
436 print_byte(h, *p);
437
438 if (breakline &&
439 (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
440 print_otag(h, TAG_BR, "");
441 breakline = 0;
442 while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
443 p++;
444 continue;
445 }
446
447 if (p >= pend)
448 break;
449
450 if (*p == ' ') {
451 print_endword(h);
452 p++;
453 continue;
454 }
455
456 if (print_escape(h, *p++))
457 continue;
458
459 esc = mandoc_escape(&p, &seq, &len);
460 switch (esc) {
461 case ESCAPE_FONT:
462 case ESCAPE_FONTPREV:
463 case ESCAPE_FONTBOLD:
464 case ESCAPE_FONTITALIC:
465 case ESCAPE_FONTBI:
466 case ESCAPE_FONTCW:
467 case ESCAPE_FONTROMAN:
468 if (0 == norecurse) {
469 h->flags |= HTML_NOSPACE;
470 if (html_setfont(h, esc))
471 print_metaf(h);
472 h->flags &= ~HTML_NOSPACE;
473 }
474 continue;
475 case ESCAPE_SKIPCHAR:
476 h->flags |= HTML_SKIPCHAR;
477 continue;
478 case ESCAPE_ERROR:
479 continue;
480 default:
481 break;
482 }
483
484 if (h->flags & HTML_SKIPCHAR) {
485 h->flags &= ~HTML_SKIPCHAR;
486 continue;
487 }
488
489 switch (esc) {
490 case ESCAPE_UNICODE:
491 /* Skip past "u" header. */
492 c = mchars_num2uc(seq + 1, len - 1);
493 break;
494 case ESCAPE_NUMBERED:
495 c = mchars_num2char(seq, len);
496 if (c < 0)
497 continue;
498 break;
499 case ESCAPE_SPECIAL:
500 c = mchars_spec2cp(seq, len);
501 if (c <= 0)
502 continue;
503 break;
504 case ESCAPE_UNDEF:
505 c = *seq;
506 break;
507 case ESCAPE_DEVICE:
508 print_word(h, "html");
509 continue;
510 case ESCAPE_BREAK:
511 breakline = 1;
512 continue;
513 case ESCAPE_NOSPACE:
514 if ('\0' == *p)
515 nospace = 1;
516 continue;
517 case ESCAPE_OVERSTRIKE:
518 if (len == 0)
519 continue;
520 c = seq[len - 1];
521 break;
522 default:
523 continue;
524 }
525 if ((c < 0x20 && c != 0x09) ||
526 (c > 0x7E && c < 0xA0))
527 c = 0xFFFD;
528 if (c > 0x7E) {
529 (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
530 print_word(h, numbuf);
531 } else if (print_escape(h, c) == 0)
532 print_byte(h, c);
533 }
534
535 return nospace;
536 }
537
538 static void
539 print_href(struct html *h, const char *name, const char *sec, int man)
540 {
541 struct stat sb;
542 const char *p, *pp;
543 char *filename;
544
545 if (man) {
546 pp = h->base_man1;
547 if (h->base_man2 != NULL) {
548 mandoc_asprintf(&filename, "%s.%s", name, sec);
549 if (stat(filename, &sb) == -1)
550 pp = h->base_man2;
551 free(filename);
552 }
553 } else
554 pp = h->base_includes;
555
556 while ((p = strchr(pp, '%')) != NULL) {
557 print_encode(h, pp, p, 1);
558 if (man && p[1] == 'S') {
559 if (sec == NULL)
560 print_byte(h, '1');
561 else
562 print_encode(h, sec, NULL, 1);
563 } else if ((man && p[1] == 'N') ||
564 (man == 0 && p[1] == 'I'))
565 print_encode(h, name, NULL, 1);
566 else
567 print_encode(h, p, p + 2, 1);
568 pp = p + 2;
569 }
570 if (*pp != '\0')
571 print_encode(h, pp, NULL, 1);
572 }
573
574 struct tag *
575 print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
576 {
577 va_list ap;
578 struct tag *t;
579 const char *attr;
580 char *arg1, *arg2;
581 int style_written, tflags;
582
583 tflags = htmltags[tag].flags;
584
585 /* Flow content is not allowed in phrasing context. */
586
587 if ((tflags & HTML_INPHRASE) == 0) {
588 for (t = h->tag; t != NULL; t = t->next) {
589 if (t->closed)
590 continue;
591 assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0);
592 break;
593 }
594
595 /*
596 * Always wrap phrasing elements in a paragraph
597 * unless already contained in some flow container;
598 * never put them directly into a section.
599 */
600
601 } else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION)
602 print_otag(h, TAG_P, "c", "Pp");
603
604 /* Push this tag onto the stack of open scopes. */
605
606 if ((tflags & HTML_NOSTACK) == 0) {
607 t = mandoc_malloc(sizeof(struct tag));
608 t->tag = tag;
609 t->next = h->tag;
610 t->refcnt = 0;
611 t->closed = 0;
612 h->tag = t;
613 } else
614 t = NULL;
615
616 if (tflags & HTML_NLBEFORE)
617 print_endline(h);
618 if (h->col == 0)
619 print_indent(h);
620 else if ((h->flags & HTML_NOSPACE) == 0) {
621 if (h->flags & HTML_KEEP)
622 print_word(h, "&#x00A0;");
623 else {
624 if (h->flags & HTML_PREKEEP)
625 h->flags |= HTML_KEEP;
626 print_endword(h);
627 }
628 }
629
630 if ( ! (h->flags & HTML_NONOSPACE))
631 h->flags &= ~HTML_NOSPACE;
632 else
633 h->flags |= HTML_NOSPACE;
634
635 /* Print out the tag name and attributes. */
636
637 print_byte(h, '<');
638 print_word(h, htmltags[tag].name);
639
640 va_start(ap, fmt);
641
642 while (*fmt != '\0' && *fmt != 's') {
643
644 /* Parse attributes and arguments. */
645
646 arg1 = va_arg(ap, char *);
647 arg2 = NULL;
648 switch (*fmt++) {
649 case 'c':
650 attr = "class";
651 break;
652 case 'h':
653 attr = "href";
654 break;
655 case 'i':
656 attr = "id";
657 break;
658 case '?':
659 attr = arg1;
660 arg1 = va_arg(ap, char *);
661 break;
662 default:
663 abort();
664 }
665 if (*fmt == 'M')
666 arg2 = va_arg(ap, char *);
667 if (arg1 == NULL)
668 continue;
669
670 /* Print the attributes. */
671
672 print_byte(h, ' ');
673 print_word(h, attr);
674 print_byte(h, '=');
675 print_byte(h, '"');
676 switch (*fmt) {
677 case 'I':
678 print_href(h, arg1, NULL, 0);
679 fmt++;
680 break;
681 case 'M':
682 print_href(h, arg1, arg2, 1);
683 fmt++;
684 break;
685 case 'R':
686 print_byte(h, '#');
687 print_encode(h, arg1, NULL, 1);
688 fmt++;
689 break;
690 default:
691 print_encode(h, arg1, NULL, 1);
692 break;
693 }
694 print_byte(h, '"');
695 }
696
697 style_written = 0;
698 while (*fmt++ == 's') {
699 arg1 = va_arg(ap, char *);
700 arg2 = va_arg(ap, char *);
701 if (arg2 == NULL)
702 continue;
703 print_byte(h, ' ');
704 if (style_written == 0) {
705 print_word(h, "style=\"");
706 style_written = 1;
707 }
708 print_word(h, arg1);
709 print_byte(h, ':');
710 print_byte(h, ' ');
711 print_word(h, arg2);
712 print_byte(h, ';');
713 }
714 if (style_written)
715 print_byte(h, '"');
716
717 va_end(ap);
718
719 /* Accommodate for "well-formed" singleton escaping. */
720
721 if (htmltags[tag].flags & HTML_NOSTACK)
722 print_byte(h, '/');
723
724 print_byte(h, '>');
725
726 if (tflags & HTML_NLBEGIN)
727 print_endline(h);
728 else
729 h->flags |= HTML_NOSPACE;
730
731 if (tflags & HTML_INDENT)
732 h->indent++;
733 if (tflags & HTML_NOINDENT)
734 h->noindent++;
735
736 return t;
737 }
738
739 static void
740 print_ctag(struct html *h, struct tag *tag)
741 {
742 int tflags;
743
744 if (tag->closed == 0) {
745 tag->closed = 1;
746 if (tag == h->metaf)
747 h->metaf = NULL;
748 if (tag == h->tblt)
749 h->tblt = NULL;
750
751 tflags = htmltags[tag->tag].flags;
752 if (tflags & HTML_INDENT)
753 h->indent--;
754 if (tflags & HTML_NOINDENT)
755 h->noindent--;
756 if (tflags & HTML_NLEND)
757 print_endline(h);
758 print_indent(h);
759 print_byte(h, '<');
760 print_byte(h, '/');
761 print_word(h, htmltags[tag->tag].name);
762 print_byte(h, '>');
763 if (tflags & HTML_NLAFTER)
764 print_endline(h);
765 }
766 if (tag->refcnt == 0) {
767 h->tag = tag->next;
768 free(tag);
769 }
770 }
771
772 void
773 print_gen_decls(struct html *h)
774 {
775 print_word(h, "<!DOCTYPE html>");
776 print_endline(h);
777 }
778
779 void
780 print_gen_comment(struct html *h, struct roff_node *n)
781 {
782 int wantblank;
783
784 print_word(h, "<!-- This is an automatically generated file."
785 " Do not edit.");
786 h->indent = 1;
787 wantblank = 0;
788 while (n != NULL && n->type == ROFFT_COMMENT) {
789 if (strstr(n->string, "-->") == NULL &&
790 (wantblank || *n->string != '\0')) {
791 print_endline(h);
792 print_indent(h);
793 print_word(h, n->string);
794 wantblank = *n->string != '\0';
795 }
796 n = n->next;
797 }
798 if (wantblank)
799 print_endline(h);
800 print_word(h, " -->");
801 print_endline(h);
802 h->indent = 0;
803 }
804
805 void
806 print_text(struct html *h, const char *word)
807 {
808 /*
809 * Always wrap text in a paragraph unless already contained in
810 * some flow container; never put it directly into a section.
811 */
812
813 if (h->tag->tag == TAG_SECTION)
814 print_otag(h, TAG_P, "c", "Pp");
815
816 /* Output whitespace before this text? */
817
818 if (h->col && (h->flags & HTML_NOSPACE) == 0) {
819 if ( ! (HTML_KEEP & h->flags)) {
820 if (HTML_PREKEEP & h->flags)
821 h->flags |= HTML_KEEP;
822 print_endword(h);
823 } else
824 print_word(h, "&#x00A0;");
825 }
826
827 /*
828 * Print the text, optionally surrounded by HTML whitespace,
829 * optionally manually switching fonts before and after.
830 */
831
832 assert(h->metaf == NULL);
833 print_metaf(h);
834 print_indent(h);
835 if ( ! print_encode(h, word, NULL, 0)) {
836 if ( ! (h->flags & HTML_NONOSPACE))
837 h->flags &= ~HTML_NOSPACE;
838 h->flags &= ~HTML_NONEWLINE;
839 } else
840 h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
841
842 if (h->metaf != NULL) {
843 print_tagq(h, h->metaf);
844 h->metaf = NULL;
845 }
846
847 h->flags &= ~HTML_IGNDELIM;
848 }
849
850 void
851 print_tagq(struct html *h, const struct tag *until)
852 {
853 struct tag *this, *next;
854
855 for (this = h->tag; this != NULL; this = next) {
856 next = this == until ? NULL : this->next;
857 print_ctag(h, this);
858 }
859 }
860
861 /*
862 * Close out all open elements up to but excluding suntil.
863 * Note that a paragraph just inside stays open together with it
864 * because paragraphs include subsequent phrasing content.
865 */
866 void
867 print_stagq(struct html *h, const struct tag *suntil)
868 {
869 struct tag *this, *next;
870
871 for (this = h->tag; this != NULL; this = next) {
872 next = this->next;
873 if (this == suntil || (next == suntil &&
874 (this->tag == TAG_P || this->tag == TAG_PRE)))
875 break;
876 print_ctag(h, this);
877 }
878 }
879
880
881 /***********************************************************************
882 * Low level output functions.
883 * They implement line breaking using a short static buffer.
884 ***********************************************************************/
885
886 /*
887 * Buffer one HTML output byte.
888 * If the buffer is full, flush and deactivate it and start a new line.
889 * If the buffer is inactive, print directly.
890 */
891 static void
892 print_byte(struct html *h, char c)
893 {
894 if ((h->flags & HTML_BUFFER) == 0) {
895 putchar(c);
896 h->col++;
897 return;
898 }
899
900 if (h->col + h->bufcol < sizeof(h->buf)) {
901 h->buf[h->bufcol++] = c;
902 return;
903 }
904
905 putchar('\n');
906 h->col = 0;
907 print_indent(h);
908 putchar(' ');
909 putchar(' ');
910 fwrite(h->buf, h->bufcol, 1, stdout);
911 putchar(c);
912 h->col = (h->indent + 1) * 2 + h->bufcol + 1;
913 h->bufcol = 0;
914 h->flags &= ~HTML_BUFFER;
915 }
916
917 /*
918 * If something was printed on the current output line, end it.
919 * Not to be called right after print_indent().
920 */
921 void
922 print_endline(struct html *h)
923 {
924 if (h->col == 0)
925 return;
926
927 if (h->bufcol) {
928 putchar(' ');
929 fwrite(h->buf, h->bufcol, 1, stdout);
930 h->bufcol = 0;
931 }
932 putchar('\n');
933 h->col = 0;
934 h->flags |= HTML_NOSPACE;
935 h->flags &= ~HTML_BUFFER;
936 }
937
938 /*
939 * Flush the HTML output buffer.
940 * If it is inactive, activate it.
941 */
942 static void
943 print_endword(struct html *h)
944 {
945 if (h->noindent) {
946 print_byte(h, ' ');
947 return;
948 }
949
950 if ((h->flags & HTML_BUFFER) == 0) {
951 h->col++;
952 h->flags |= HTML_BUFFER;
953 } else if (h->bufcol) {
954 putchar(' ');
955 fwrite(h->buf, h->bufcol, 1, stdout);
956 h->col += h->bufcol + 1;
957 }
958 h->bufcol = 0;
959 }
960
961 /*
962 * If at the beginning of a new output line,
963 * perform indentation and mark the line as containing output.
964 * Make sure to really produce some output right afterwards,
965 * but do not use print_otag() for producing it.
966 */
967 static void
968 print_indent(struct html *h)
969 {
970 size_t i;
971
972 if (h->col || h->noindent)
973 return;
974
975 h->col = h->indent * 2;
976 for (i = 0; i < h->col; i++)
977 putchar(' ');
978 }
979
980 /*
981 * Print or buffer some characters
982 * depending on the current HTML output buffer state.
983 */
984 static void
985 print_word(struct html *h, const char *cp)
986 {
987 while (*cp != '\0')
988 print_byte(h, *cp++);
989 }