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