Annotation of src/usr.bin/mandoc/html.c, Revision 1.105
1.105 ! schwarze 1: /* $OpenBSD: html.c,v 1.104 2018/06/25 13:26:53 schwarze Exp $ */
1.1 schwarze 2: /*
1.42 schwarze 3: * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
1.91 schwarze 4: * Copyright (c) 2011-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org>
1.1 schwarze 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: *
1.56 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.1 schwarze 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.56 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.1 schwarze 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 <sys/types.h>
19:
20: #include <assert.h>
1.3 schwarze 21: #include <ctype.h>
1.4 schwarze 22: #include <stdarg.h>
1.99 schwarze 23: #include <stddef.h>
1.1 schwarze 24: #include <stdio.h>
25: #include <stdint.h>
26: #include <stdlib.h>
27: #include <string.h>
28: #include <unistd.h>
29:
1.80 schwarze 30: #include "mandoc_aux.h"
1.99 schwarze 31: #include "mandoc_ohash.h"
1.9 schwarze 32: #include "mandoc.h"
1.80 schwarze 33: #include "roff.h"
1.1 schwarze 34: #include "out.h"
35: #include "html.h"
1.56 schwarze 36: #include "manconf.h"
1.1 schwarze 37: #include "main.h"
38:
39: struct htmldata {
40: const char *name;
41: int flags;
1.66 schwarze 42: #define HTML_NOSTACK (1 << 0)
43: #define HTML_AUTOCLOSE (1 << 1)
44: #define HTML_NLBEFORE (1 << 2)
45: #define HTML_NLBEGIN (1 << 3)
46: #define HTML_NLEND (1 << 4)
47: #define HTML_NLAFTER (1 << 5)
48: #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER)
49: #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND)
50: #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE)
51: #define HTML_INDENT (1 << 6)
52: #define HTML_NOINDENT (1 << 7)
1.1 schwarze 53: };
54:
55: static const struct htmldata htmltags[TAG_MAX] = {
1.66 schwarze 56: {"html", HTML_NLALL},
57: {"head", HTML_NLALL | HTML_INDENT},
58: {"body", HTML_NLALL},
59: {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
60: {"title", HTML_NLAROUND},
61: {"div", HTML_NLAROUND},
1.95 schwarze 62: {"div", 0},
1.66 schwarze 63: {"h1", HTML_NLAROUND},
64: {"h2", HTML_NLAROUND},
65: {"span", 0},
66: {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
67: {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL},
68: {"a", 0},
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: {"pre", HTML_NLALL | HTML_NOINDENT},
1.77 schwarze 79: {"var", 0},
1.76 schwarze 80: {"cite", 0},
1.66 schwarze 81: {"b", 0},
82: {"i", 0},
83: {"code", 0},
84: {"small", 0},
85: {"style", HTML_NLALL | HTML_INDENT},
86: {"math", HTML_NLALL | HTML_INDENT},
87: {"mrow", 0},
88: {"mi", 0},
1.85 schwarze 89: {"mn", 0},
1.66 schwarze 90: {"mo", 0},
91: {"msup", 0},
92: {"msub", 0},
93: {"msubsup", 0},
94: {"mfrac", 0},
95: {"msqrt", 0},
96: {"mfenced", 0},
97: {"mtable", 0},
98: {"mtr", 0},
99: {"mtd", 0},
100: {"munderover", 0},
101: {"munder", 0},
102: {"mover", 0},
1.5 schwarze 103: };
104:
1.26 schwarze 105: static const char *const roffscales[SCALE_MAX] = {
106: "cm", /* SCALE_CM */
107: "in", /* SCALE_IN */
108: "pc", /* SCALE_PC */
109: "pt", /* SCALE_PT */
110: "em", /* SCALE_EM */
111: "em", /* SCALE_MM */
112: "ex", /* SCALE_EN */
113: "ex", /* SCALE_BU */
114: "em", /* SCALE_VS */
115: "ex", /* SCALE_FS */
116: };
1.5 schwarze 117:
1.99 schwarze 118: /* Avoid duplicate HTML id= attributes. */
119: static struct ohash id_unique;
120:
1.64 schwarze 121: static void a2width(const char *, struct roffsu *);
1.67 schwarze 122: static void print_byte(struct html *, char);
123: static void print_endword(struct html *);
124: static void print_indent(struct html *);
125: static void print_word(struct html *, const char *);
126:
1.54 schwarze 127: static void print_ctag(struct html *, struct tag *);
1.67 schwarze 128: static int print_escape(struct html *, char);
1.65 schwarze 129: static int print_encode(struct html *, const char *, const char *, int);
130: static void print_href(struct html *, const char *, const char *, int);
1.26 schwarze 131: static void print_metaf(struct html *, enum mandoc_esc);
1.5 schwarze 132:
1.35 schwarze 133:
1.50 schwarze 134: void *
1.61 schwarze 135: html_alloc(const struct manoutput *outopts)
1.1 schwarze 136: {
137: struct html *h;
138:
1.24 schwarze 139: h = mandoc_calloc(1, sizeof(struct html));
1.1 schwarze 140:
1.74 schwarze 141: h->tag = NULL;
1.56 schwarze 142: h->style = outopts->style;
143: h->base_man = outopts->man;
144: h->base_includes = outopts->includes;
145: if (outopts->fragment)
146: h->oflags |= HTML_FRAGMENT;
1.1 schwarze 147:
1.99 schwarze 148: mandoc_ohash_init(&id_unique, 4, 0);
149:
1.58 schwarze 150: return h;
1.1 schwarze 151: }
152:
153: void
154: html_free(void *p)
155: {
156: struct tag *tag;
157: struct html *h;
1.99 schwarze 158: char *cp;
159: unsigned int slot;
1.1 schwarze 160:
161: h = (struct html *)p;
1.74 schwarze 162: while ((tag = h->tag) != NULL) {
163: h->tag = tag->next;
1.1 schwarze 164: free(tag);
165: }
1.99 schwarze 166: free(h);
1.1 schwarze 167:
1.99 schwarze 168: cp = ohash_first(&id_unique, &slot);
169: while (cp != NULL) {
170: free(cp);
171: cp = ohash_next(&id_unique, &slot);
172: }
173: ohash_delete(&id_unique);
1.1 schwarze 174: }
175:
176: void
177: print_gen_head(struct html *h)
178: {
1.42 schwarze 179: struct tag *t;
180:
1.64 schwarze 181: print_otag(h, TAG_META, "?", "charset", "utf-8");
1.92 schwarze 182: if (h->style != NULL) {
183: print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet",
184: h->style, "type", "text/css", "media", "all");
185: return;
186: }
1.1 schwarze 187:
1.42 schwarze 188: /*
1.92 schwarze 189: * Print a minimal embedded style sheet.
1.42 schwarze 190: */
1.66 schwarze 191:
1.64 schwarze 192: t = print_otag(h, TAG_STYLE, "");
1.66 schwarze 193: print_text(h, "table.head, table.foot { width: 100%; }");
1.67 schwarze 194: print_endline(h);
1.66 schwarze 195: print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }");
1.67 schwarze 196: print_endline(h);
1.66 schwarze 197: print_text(h, "td.head-vol { text-align: center; }");
1.67 schwarze 198: print_endline(h);
1.68 schwarze 199: print_text(h, "div.Pp { margin: 1ex 0ex; }");
1.95 schwarze 200: print_endline(h);
201: print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }");
1.96 schwarze 202: print_endline(h);
1.97 schwarze 203: print_text(h, "span.Pa, span.Ad { font-style: italic; }");
1.98 schwarze 204: print_endline(h);
205: print_text(h, "span.Ms { font-weight: bold; }");
1.94 schwarze 206: print_endline(h);
207: print_text(h, "dl.Bl-diag ");
208: print_byte(h, '>');
209: print_text(h, " dt { font-weight: bold; }");
1.93 schwarze 210: print_endline(h);
211: print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, "
212: "code.In, code.Fd, code.Fn,");
213: print_endline(h);
214: print_text(h, "code.Cd { font-weight: bold; "
215: "font-family: inherit; }");
1.42 schwarze 216: print_tagq(h, t);
1.1 schwarze 217: }
218:
1.5 schwarze 219: static void
1.26 schwarze 220: print_metaf(struct html *h, enum mandoc_esc deco)
1.5 schwarze 221: {
222: enum htmlfont font;
1.1 schwarze 223:
1.5 schwarze 224: switch (deco) {
1.35 schwarze 225: case ESCAPE_FONTPREV:
1.5 schwarze 226: font = h->metal;
227: break;
1.35 schwarze 228: case ESCAPE_FONTITALIC:
1.5 schwarze 229: font = HTMLFONT_ITALIC;
230: break;
1.35 schwarze 231: case ESCAPE_FONTBOLD:
1.5 schwarze 232: font = HTMLFONT_BOLD;
233: break;
1.35 schwarze 234: case ESCAPE_FONTBI:
1.31 schwarze 235: font = HTMLFONT_BI;
236: break;
1.35 schwarze 237: case ESCAPE_FONT:
238: case ESCAPE_FONTROMAN:
1.5 schwarze 239: font = HTMLFONT_NONE;
240: break;
241: default:
242: abort();
1.1 schwarze 243: }
244:
1.20 schwarze 245: if (h->metaf) {
246: print_tagq(h, h->metaf);
247: h->metaf = NULL;
248: }
249:
250: h->metal = h->metac;
251: h->metac = font;
252:
1.31 schwarze 253: switch (font) {
1.35 schwarze 254: case HTMLFONT_ITALIC:
1.64 schwarze 255: h->metaf = print_otag(h, TAG_I, "");
1.31 schwarze 256: break;
1.35 schwarze 257: case HTMLFONT_BOLD:
1.64 schwarze 258: h->metaf = print_otag(h, TAG_B, "");
1.31 schwarze 259: break;
1.35 schwarze 260: case HTMLFONT_BI:
1.64 schwarze 261: h->metaf = print_otag(h, TAG_B, "");
262: print_otag(h, TAG_I, "");
1.31 schwarze 263: break;
264: default:
265: break;
266: }
1.80 schwarze 267: }
268:
269: char *
1.99 schwarze 270: html_make_id(const struct roff_node *n, int unique)
1.80 schwarze 271: {
272: const struct roff_node *nch;
1.99 schwarze 273: char *buf, *bufs, *cp;
274: unsigned int slot;
275: int suffix;
1.80 schwarze 276:
277: for (nch = n->child; nch != NULL; nch = nch->next)
278: if (nch->type != ROFFT_TEXT)
279: return NULL;
280:
281: buf = NULL;
282: deroff(&buf, n);
1.90 schwarze 283: if (buf == NULL)
284: return NULL;
1.80 schwarze 285:
1.100 schwarze 286: /*
287: * In ID attributes, only use ASCII characters that are
288: * permitted in URL-fragment strings according to the
289: * explicit list at:
290: * https://url.spec.whatwg.org/#url-fragment-string
291: */
1.80 schwarze 292:
293: for (cp = buf; *cp != '\0'; cp++)
1.100 schwarze 294: if (isalnum((unsigned char)*cp) == 0 &&
295: strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL)
1.80 schwarze 296: *cp = '_';
297:
1.99 schwarze 298: if (unique == 0)
299: return buf;
300:
301: /* Avoid duplicate HTML id= attributes. */
302:
303: bufs = NULL;
304: suffix = 1;
305: slot = ohash_qlookup(&id_unique, buf);
306: cp = ohash_find(&id_unique, slot);
307: if (cp != NULL) {
308: while (cp != NULL) {
309: free(bufs);
310: if (++suffix > 127) {
311: free(buf);
312: return NULL;
313: }
314: mandoc_asprintf(&bufs, "%s_%d", buf, suffix);
315: slot = ohash_qlookup(&id_unique, bufs);
316: cp = ohash_find(&id_unique, slot);
317: }
318: free(buf);
319: buf = bufs;
320: }
321: ohash_insert(&id_unique, slot, buf);
1.80 schwarze 322: return buf;
1.1 schwarze 323: }
324:
1.26 schwarze 325: int
326: html_strlen(const char *cp)
327: {
1.30 schwarze 328: size_t rsz;
329: int skip, sz;
1.26 schwarze 330:
331: /*
332: * Account for escaped sequences within string length
333: * calculations. This follows the logic in term_strlen() as we
334: * must calculate the width of produced strings.
335: * Assume that characters are always width of "1". This is
336: * hacky, but it gets the job done for approximation of widths.
337: */
338:
339: sz = 0;
1.30 schwarze 340: skip = 0;
341: while (1) {
342: rsz = strcspn(cp, "\\");
343: if (rsz) {
344: cp += rsz;
345: if (skip) {
346: skip = 0;
347: rsz--;
348: }
349: sz += rsz;
350: }
351: if ('\0' == *cp)
352: break;
353: cp++;
354: switch (mandoc_escape(&cp, NULL, NULL)) {
1.35 schwarze 355: case ESCAPE_ERROR:
1.58 schwarze 356: return sz;
1.35 schwarze 357: case ESCAPE_UNICODE:
358: case ESCAPE_NUMBERED:
359: case ESCAPE_SPECIAL:
1.55 schwarze 360: case ESCAPE_OVERSTRIKE:
1.30 schwarze 361: if (skip)
362: skip = 0;
363: else
364: sz++;
365: break;
1.35 schwarze 366: case ESCAPE_SKIPCHAR:
1.30 schwarze 367: skip = 1;
1.26 schwarze 368: break;
369: default:
370: break;
371: }
372: }
1.58 schwarze 373: return sz;
1.26 schwarze 374: }
1.1 schwarze 375:
1.5 schwarze 376: static int
1.67 schwarze 377: print_escape(struct html *h, char c)
1.38 schwarze 378: {
379:
380: switch (c) {
381: case '<':
1.67 schwarze 382: print_word(h, "<");
1.38 schwarze 383: break;
384: case '>':
1.67 schwarze 385: print_word(h, ">");
1.38 schwarze 386: break;
387: case '&':
1.67 schwarze 388: print_word(h, "&");
1.38 schwarze 389: break;
390: case '"':
1.67 schwarze 391: print_word(h, """);
1.38 schwarze 392: break;
393: case ASCII_NBRSP:
1.67 schwarze 394: print_word(h, " ");
1.38 schwarze 395: break;
396: case ASCII_HYPH:
1.67 schwarze 397: print_byte(h, '-');
1.59 schwarze 398: break;
1.38 schwarze 399: case ASCII_BREAK:
400: break;
401: default:
1.58 schwarze 402: return 0;
1.38 schwarze 403: }
1.58 schwarze 404: return 1;
1.38 schwarze 405: }
406:
407: static int
1.65 schwarze 408: print_encode(struct html *h, const char *p, const char *pend, int norecurse)
1.1 schwarze 409: {
1.67 schwarze 410: char numbuf[16];
1.84 schwarze 411: struct tag *t;
412: const char *seq;
1.4 schwarze 413: size_t sz;
1.84 schwarze 414: int c, len, breakline, nospace;
1.26 schwarze 415: enum mandoc_esc esc;
1.84 schwarze 416: static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"',
1.33 schwarze 417: ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
1.5 schwarze 418:
1.65 schwarze 419: if (pend == NULL)
420: pend = strchr(p, '\0');
421:
1.84 schwarze 422: breakline = 0;
1.5 schwarze 423: nospace = 0;
1.1 schwarze 424:
1.65 schwarze 425: while (p < pend) {
1.30 schwarze 426: if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
427: h->flags &= ~HTML_SKIPCHAR;
428: p++;
429: continue;
430: }
431:
1.67 schwarze 432: for (sz = strcspn(p, rejs); sz-- && p < pend; p++)
1.84 schwarze 433: print_byte(h, *p);
434:
435: if (breakline &&
436: (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) {
437: t = print_otag(h, TAG_DIV, "");
438: print_text(h, "\\~");
439: print_tagq(h, t);
440: breakline = 0;
441: while (p < pend && (*p == ' ' || *p == ASCII_NBRSP))
442: p++;
443: continue;
444: }
1.4 schwarze 445:
1.65 schwarze 446: if (p >= pend)
1.26 schwarze 447: break;
448:
1.84 schwarze 449: if (*p == ' ') {
450: print_endword(h);
451: p++;
452: continue;
453: }
454:
1.67 schwarze 455: if (print_escape(h, *p++))
1.33 schwarze 456: continue;
1.4 schwarze 457:
1.26 schwarze 458: esc = mandoc_escape(&p, &seq, &len);
459: if (ESCAPE_ERROR == esc)
460: break;
1.5 schwarze 461:
1.26 schwarze 462: switch (esc) {
1.35 schwarze 463: case ESCAPE_FONT:
464: case ESCAPE_FONTPREV:
465: case ESCAPE_FONTBOLD:
466: case ESCAPE_FONTITALIC:
467: case ESCAPE_FONTBI:
468: case ESCAPE_FONTROMAN:
1.30 schwarze 469: if (0 == norecurse)
470: print_metaf(h, esc);
471: continue;
1.35 schwarze 472: case ESCAPE_SKIPCHAR:
1.30 schwarze 473: h->flags |= HTML_SKIPCHAR;
474: continue;
475: default:
476: break;
477: }
478:
479: if (h->flags & HTML_SKIPCHAR) {
480: h->flags &= ~HTML_SKIPCHAR;
481: continue;
482: }
483:
484: switch (esc) {
1.35 schwarze 485: case ESCAPE_UNICODE:
1.38 schwarze 486: /* Skip past "u" header. */
1.26 schwarze 487: c = mchars_num2uc(seq + 1, len - 1);
488: break;
1.35 schwarze 489: case ESCAPE_NUMBERED:
1.26 schwarze 490: c = mchars_num2char(seq, len);
1.51 schwarze 491: if (c < 0)
492: continue;
1.26 schwarze 493: break;
1.35 schwarze 494: case ESCAPE_SPECIAL:
1.61 schwarze 495: c = mchars_spec2cp(seq, len);
1.51 schwarze 496: if (c <= 0)
497: continue;
1.26 schwarze 498: break;
1.84 schwarze 499: case ESCAPE_BREAK:
500: breakline = 1;
501: continue;
1.35 schwarze 502: case ESCAPE_NOSPACE:
1.26 schwarze 503: if ('\0' == *p)
504: nospace = 1;
1.49 schwarze 505: continue;
1.55 schwarze 506: case ESCAPE_OVERSTRIKE:
507: if (len == 0)
508: continue;
509: c = seq[len - 1];
510: break;
1.5 schwarze 511: default:
1.49 schwarze 512: continue;
1.5 schwarze 513: }
1.51 schwarze 514: if ((c < 0x20 && c != 0x09) ||
515: (c > 0x7E && c < 0xA0))
1.49 schwarze 516: c = 0xFFFD;
1.67 schwarze 517: if (c > 0x7E) {
1.86 bentley 518: (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c);
1.67 schwarze 519: print_word(h, numbuf);
520: } else if (print_escape(h, c) == 0)
521: print_byte(h, c);
1.1 schwarze 522: }
1.5 schwarze 523:
1.58 schwarze 524: return nospace;
1.1 schwarze 525: }
526:
1.6 schwarze 527: static void
1.65 schwarze 528: print_href(struct html *h, const char *name, const char *sec, int man)
1.6 schwarze 529: {
1.65 schwarze 530: const char *p, *pp;
531:
532: pp = man ? h->base_man : h->base_includes;
533: while ((p = strchr(pp, '%')) != NULL) {
534: print_encode(h, pp, p, 1);
535: if (man && p[1] == 'S') {
536: if (sec == NULL)
1.67 schwarze 537: print_byte(h, '1');
1.65 schwarze 538: else
539: print_encode(h, sec, NULL, 1);
540: } else if ((man && p[1] == 'N') ||
541: (man == 0 && p[1] == 'I'))
542: print_encode(h, name, NULL, 1);
543: else
544: print_encode(h, p, p + 2, 1);
545: pp = p + 2;
546: }
547: if (*pp != '\0')
548: print_encode(h, pp, NULL, 1);
1.6 schwarze 549: }
550:
1.1 schwarze 551: struct tag *
1.64 schwarze 552: print_otag(struct html *h, enum htmltag tag, const char *fmt, ...)
1.1 schwarze 553: {
1.64 schwarze 554: va_list ap;
555: struct roffsu mysu, *su;
1.67 schwarze 556: char numbuf[16];
1.1 schwarze 557: struct tag *t;
1.65 schwarze 558: const char *attr;
1.73 schwarze 559: char *arg1, *arg2;
1.65 schwarze 560: double v;
1.66 schwarze 561: int i, have_style, tflags;
562:
563: tflags = htmltags[tag].flags;
1.1 schwarze 564:
1.74 schwarze 565: /* Push this tag onto the stack of open scopes. */
1.6 schwarze 566:
1.66 schwarze 567: if ((tflags & HTML_NOSTACK) == 0) {
1.24 schwarze 568: t = mandoc_malloc(sizeof(struct tag));
1.1 schwarze 569: t->tag = tag;
1.74 schwarze 570: t->next = h->tag;
571: h->tag = t;
1.1 schwarze 572: } else
573: t = NULL;
574:
1.66 schwarze 575: if (tflags & HTML_NLBEFORE)
1.67 schwarze 576: print_endline(h);
577: if (h->col == 0)
578: print_indent(h);
1.66 schwarze 579: else if ((h->flags & HTML_NOSPACE) == 0) {
580: if (h->flags & HTML_KEEP)
1.86 bentley 581: print_word(h, " ");
1.66 schwarze 582: else {
583: if (h->flags & HTML_PREKEEP)
584: h->flags |= HTML_KEEP;
1.67 schwarze 585: print_endword(h);
1.12 schwarze 586: }
1.66 schwarze 587: }
1.1 schwarze 588:
1.13 schwarze 589: if ( ! (h->flags & HTML_NONOSPACE))
590: h->flags &= ~HTML_NOSPACE;
1.14 schwarze 591: else
592: h->flags |= HTML_NOSPACE;
1.13 schwarze 593:
1.6 schwarze 594: /* Print out the tag name and attributes. */
595:
1.67 schwarze 596: print_byte(h, '<');
597: print_word(h, htmltags[tag].name);
1.64 schwarze 598:
599: va_start(ap, fmt);
600:
601: have_style = 0;
602: while (*fmt != '\0') {
603: if (*fmt == 's') {
604: have_style = 1;
605: fmt++;
606: break;
607: }
1.73 schwarze 608:
609: /* Parse a non-style attribute and its arguments. */
610:
611: arg1 = va_arg(ap, char *);
1.64 schwarze 612: switch (*fmt++) {
613: case 'c':
1.65 schwarze 614: attr = "class";
1.64 schwarze 615: break;
616: case 'h':
1.65 schwarze 617: attr = "href";
1.64 schwarze 618: break;
619: case 'i':
1.65 schwarze 620: attr = "id";
1.64 schwarze 621: break;
622: case '?':
1.73 schwarze 623: attr = arg1;
624: arg1 = va_arg(ap, char *);
1.64 schwarze 625: break;
626: default:
627: abort();
628: }
1.73 schwarze 629: arg2 = NULL;
630: if (*fmt == 'M')
631: arg2 = va_arg(ap, char *);
632: if (arg1 == NULL)
633: continue;
634:
635: /* Print the non-style attributes. */
636:
1.67 schwarze 637: print_byte(h, ' ');
638: print_word(h, attr);
639: print_byte(h, '=');
640: print_byte(h, '"');
1.65 schwarze 641: switch (*fmt) {
1.78 schwarze 642: case 'I':
643: print_href(h, arg1, NULL, 0);
644: fmt++;
645: break;
1.65 schwarze 646: case 'M':
1.73 schwarze 647: print_href(h, arg1, arg2, 1);
1.65 schwarze 648: fmt++;
649: break;
1.78 schwarze 650: case 'R':
651: print_byte(h, '#');
652: print_encode(h, arg1, NULL, 1);
1.65 schwarze 653: fmt++;
654: break;
1.78 schwarze 655: case 'T':
656: print_encode(h, arg1, NULL, 1);
657: print_word(h, "\" title=\"");
658: print_encode(h, arg1, NULL, 1);
1.65 schwarze 659: fmt++;
1.78 schwarze 660: break;
1.65 schwarze 661: default:
1.73 schwarze 662: print_encode(h, arg1, NULL, 1);
1.65 schwarze 663: break;
664: }
1.67 schwarze 665: print_byte(h, '"');
1.64 schwarze 666: }
667:
668: /* Print out styles. */
669:
670: while (*fmt != '\0') {
1.73 schwarze 671: arg1 = NULL;
672: su = NULL;
1.64 schwarze 673:
674: /* First letter: input argument type. */
675:
676: switch (*fmt++) {
677: case 'h':
678: i = va_arg(ap, int);
1.73 schwarze 679: su = &mysu;
1.64 schwarze 680: SCALE_HS_INIT(su, i);
681: break;
682: case 's':
1.73 schwarze 683: arg1 = va_arg(ap, char *);
1.64 schwarze 684: break;
685: case 'u':
686: su = va_arg(ap, struct roffsu *);
687: break;
688: case 'w':
1.89 schwarze 689: if ((arg2 = va_arg(ap, char *)) != NULL) {
690: su = &mysu;
691: a2width(arg2, su);
692: }
1.81 schwarze 693: if (*fmt == '+') {
1.89 schwarze 694: if (su != NULL) {
695: /* Make even bold text fit. */
696: su->scale *= 1.2;
697: /* Add padding. */
698: su->scale += 3.0;
699: }
1.81 schwarze 700: fmt++;
701: }
1.64 schwarze 702: break;
703: default:
704: abort();
705: }
706:
707: /* Second letter: style name. */
708:
709: switch (*fmt++) {
1.103 schwarze 710: case 'h':
711: attr = "height";
1.64 schwarze 712: break;
713: case 'l':
1.65 schwarze 714: attr = "margin-left";
1.64 schwarze 715: break;
716: case 'w':
1.65 schwarze 717: attr = "width";
1.64 schwarze 718: break;
719: case 'W':
1.65 schwarze 720: attr = "min-width";
1.64 schwarze 721: break;
722: case '?':
1.73 schwarze 723: attr = arg1;
724: arg1 = va_arg(ap, char *);
725: break;
1.64 schwarze 726: default:
727: abort();
728: }
1.73 schwarze 729: if (su == NULL && arg1 == NULL)
730: continue;
731:
732: if (have_style == 1)
733: print_word(h, " style=\"");
734: else
735: print_byte(h, ' ');
1.67 schwarze 736: print_word(h, attr);
737: print_byte(h, ':');
738: print_byte(h, ' ');
1.73 schwarze 739: if (su != NULL) {
740: v = su->scale;
741: if (su->unit == SCALE_MM && (v /= 100.0) == 0.0)
742: v = 1.0;
743: else if (su->unit == SCALE_BU)
744: v /= 24.0;
745: (void)snprintf(numbuf, sizeof(numbuf), "%.2f", v);
746: print_word(h, numbuf);
747: print_word(h, roffscales[su->unit]);
748: } else
749: print_word(h, arg1);
1.67 schwarze 750: print_byte(h, ';');
1.73 schwarze 751: have_style = 2;
1.64 schwarze 752: }
1.73 schwarze 753: if (have_style == 2)
1.67 schwarze 754: print_byte(h, '"');
1.64 schwarze 755:
756: va_end(ap);
1.6 schwarze 757:
1.42 schwarze 758: /* Accommodate for "well-formed" singleton escaping. */
1.6 schwarze 759:
760: if (HTML_AUTOCLOSE & htmltags[tag].flags)
1.67 schwarze 761: print_byte(h, '/');
1.6 schwarze 762:
1.67 schwarze 763: print_byte(h, '>');
1.1 schwarze 764:
1.66 schwarze 765: if (tflags & HTML_NLBEGIN)
1.67 schwarze 766: print_endline(h);
1.66 schwarze 767: else
768: h->flags |= HTML_NOSPACE;
1.18 schwarze 769:
1.66 schwarze 770: if (tflags & HTML_INDENT)
771: h->indent++;
772: if (tflags & HTML_NOINDENT)
773: h->noindent++;
1.18 schwarze 774:
1.58 schwarze 775: return t;
1.1 schwarze 776: }
777:
778: static void
1.54 schwarze 779: print_ctag(struct html *h, struct tag *tag)
1.1 schwarze 780: {
1.66 schwarze 781: int tflags;
1.35 schwarze 782:
1.54 schwarze 783: /*
784: * Remember to close out and nullify the current
785: * meta-font and table, if applicable.
786: */
787: if (tag == h->metaf)
788: h->metaf = NULL;
789: if (tag == h->tblt)
790: h->tblt = NULL;
791:
1.66 schwarze 792: tflags = htmltags[tag->tag].flags;
793:
794: if (tflags & HTML_INDENT)
795: h->indent--;
796: if (tflags & HTML_NOINDENT)
797: h->noindent--;
798: if (tflags & HTML_NLEND)
1.67 schwarze 799: print_endline(h);
800: print_indent(h);
801: print_byte(h, '<');
802: print_byte(h, '/');
803: print_word(h, htmltags[tag->tag].name);
804: print_byte(h, '>');
1.66 schwarze 805: if (tflags & HTML_NLAFTER)
1.67 schwarze 806: print_endline(h);
1.54 schwarze 807:
1.74 schwarze 808: h->tag = tag->next;
1.54 schwarze 809: free(tag);
1.1 schwarze 810: }
811:
812: void
1.6 schwarze 813: print_gen_decls(struct html *h)
814: {
1.67 schwarze 815: print_word(h, "<!DOCTYPE html>");
816: print_endline(h);
1.91 schwarze 817: }
818:
819: void
820: print_gen_comment(struct html *h, struct roff_node *n)
821: {
822: int wantblank;
823:
824: print_word(h, "<!-- This is an automatically generated file."
825: " Do not edit.");
826: h->indent = 1;
827: wantblank = 0;
828: while (n != NULL && n->type == ROFFT_COMMENT) {
829: if (strstr(n->string, "-->") == NULL &&
830: (wantblank || *n->string != '\0')) {
831: print_endline(h);
832: print_indent(h);
833: print_word(h, n->string);
834: wantblank = *n->string != '\0';
835: }
836: n = n->next;
837: }
838: if (wantblank)
839: print_endline(h);
840: print_word(h, " -->");
841: print_endline(h);
842: h->indent = 0;
1.1 schwarze 843: }
844:
845: void
1.12 schwarze 846: print_text(struct html *h, const char *word)
1.1 schwarze 847: {
1.67 schwarze 848: if (h->col && (h->flags & HTML_NOSPACE) == 0) {
1.12 schwarze 849: if ( ! (HTML_KEEP & h->flags)) {
850: if (HTML_PREKEEP & h->flags)
851: h->flags |= HTML_KEEP;
1.67 schwarze 852: print_endword(h);
1.12 schwarze 853: } else
1.86 bentley 854: print_word(h, " ");
1.12 schwarze 855: }
1.1 schwarze 856:
1.20 schwarze 857: assert(NULL == h->metaf);
1.31 schwarze 858: switch (h->metac) {
1.35 schwarze 859: case HTMLFONT_ITALIC:
1.64 schwarze 860: h->metaf = print_otag(h, TAG_I, "");
1.31 schwarze 861: break;
1.35 schwarze 862: case HTMLFONT_BOLD:
1.64 schwarze 863: h->metaf = print_otag(h, TAG_B, "");
1.31 schwarze 864: break;
1.35 schwarze 865: case HTMLFONT_BI:
1.64 schwarze 866: h->metaf = print_otag(h, TAG_B, "");
867: print_otag(h, TAG_I, "");
1.31 schwarze 868: break;
869: default:
1.67 schwarze 870: print_indent(h);
1.31 schwarze 871: break;
872: }
1.20 schwarze 873:
1.12 schwarze 874: assert(word);
1.65 schwarze 875: if ( ! print_encode(h, word, NULL, 0)) {
1.13 schwarze 876: if ( ! (h->flags & HTML_NONOSPACE))
877: h->flags &= ~HTML_NOSPACE;
1.53 schwarze 878: h->flags &= ~HTML_NONEWLINE;
1.28 schwarze 879: } else
1.53 schwarze 880: h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
1.20 schwarze 881:
882: if (h->metaf) {
883: print_tagq(h, h->metaf);
884: h->metaf = NULL;
885: }
1.17 schwarze 886:
887: h->flags &= ~HTML_IGNDELIM;
1.1 schwarze 888: }
889:
890: void
891: print_tagq(struct html *h, const struct tag *until)
892: {
893: struct tag *tag;
894:
1.74 schwarze 895: while ((tag = h->tag) != NULL) {
1.54 schwarze 896: print_ctag(h, tag);
1.1 schwarze 897: if (until && tag == until)
898: return;
899: }
900: }
901:
902: void
903: print_stagq(struct html *h, const struct tag *suntil)
904: {
905: struct tag *tag;
906:
1.74 schwarze 907: while ((tag = h->tag) != NULL) {
1.1 schwarze 908: if (suntil && tag == suntil)
909: return;
1.54 schwarze 910: print_ctag(h, tag);
1.1 schwarze 911: }
912: }
1.42 schwarze 913:
914: void
915: print_paragraph(struct html *h)
916: {
917: struct tag *t;
918:
1.68 schwarze 919: t = print_otag(h, TAG_DIV, "c", "Pp");
1.42 schwarze 920: print_tagq(h, t);
921: }
922:
1.67 schwarze 923:
924: /***********************************************************************
925: * Low level output functions.
926: * They implement line breaking using a short static buffer.
927: ***********************************************************************/
928:
929: /*
930: * Buffer one HTML output byte.
931: * If the buffer is full, flush and deactivate it and start a new line.
932: * If the buffer is inactive, print directly.
933: */
934: static void
935: print_byte(struct html *h, char c)
936: {
937: if ((h->flags & HTML_BUFFER) == 0) {
938: putchar(c);
939: h->col++;
940: return;
941: }
942:
943: if (h->col + h->bufcol < sizeof(h->buf)) {
944: h->buf[h->bufcol++] = c;
945: return;
946: }
947:
948: putchar('\n');
949: h->col = 0;
950: print_indent(h);
951: putchar(' ');
952: putchar(' ');
953: fwrite(h->buf, h->bufcol, 1, stdout);
954: putchar(c);
955: h->col = (h->indent + 1) * 2 + h->bufcol + 1;
956: h->bufcol = 0;
957: h->flags &= ~HTML_BUFFER;
958: }
959:
1.66 schwarze 960: /*
961: * If something was printed on the current output line, end it.
1.67 schwarze 962: * Not to be called right after print_indent().
1.66 schwarze 963: */
1.72 schwarze 964: void
1.67 schwarze 965: print_endline(struct html *h)
1.66 schwarze 966: {
1.67 schwarze 967: if (h->col == 0)
1.66 schwarze 968: return;
969:
1.67 schwarze 970: if (h->bufcol) {
971: putchar(' ');
972: fwrite(h->buf, h->bufcol, 1, stdout);
973: h->bufcol = 0;
974: }
1.66 schwarze 975: putchar('\n');
1.67 schwarze 976: h->col = 0;
977: h->flags |= HTML_NOSPACE;
978: h->flags &= ~HTML_BUFFER;
979: }
980:
981: /*
982: * Flush the HTML output buffer.
983: * If it is inactive, activate it.
984: */
985: static void
986: print_endword(struct html *h)
987: {
988: if (h->noindent) {
989: print_byte(h, ' ');
990: return;
991: }
992:
993: if ((h->flags & HTML_BUFFER) == 0) {
994: h->col++;
995: h->flags |= HTML_BUFFER;
996: } else if (h->bufcol) {
997: putchar(' ');
998: fwrite(h->buf, h->bufcol, 1, stdout);
999: h->col += h->bufcol + 1;
1000: }
1001: h->bufcol = 0;
1.66 schwarze 1002: }
1003:
1004: /*
1005: * If at the beginning of a new output line,
1006: * perform indentation and mark the line as containing output.
1007: * Make sure to really produce some output right afterwards,
1008: * but do not use print_otag() for producing it.
1009: */
1010: static void
1.67 schwarze 1011: print_indent(struct html *h)
1.66 schwarze 1012: {
1.67 schwarze 1013: size_t i;
1.66 schwarze 1014:
1.67 schwarze 1015: if (h->col)
1.66 schwarze 1016: return;
1017:
1.67 schwarze 1018: if (h->noindent == 0) {
1019: h->col = h->indent * 2;
1020: for (i = 0; i < h->col; i++)
1.66 schwarze 1021: putchar(' ');
1.67 schwarze 1022: }
1023: h->flags &= ~HTML_NOSPACE;
1024: }
1025:
1026: /*
1027: * Print or buffer some characters
1028: * depending on the current HTML output buffer state.
1029: */
1030: static void
1031: print_word(struct html *h, const char *cp)
1032: {
1033: while (*cp != '\0')
1034: print_byte(h, *cp++);
1.66 schwarze 1035: }
1.64 schwarze 1036:
1037: /*
1038: * Calculate the scaling unit passed in a `-width' argument. This uses
1039: * either a native scaling unit (e.g., 1i, 2m) or the string length of
1040: * the value.
1041: */
1042: static void
1043: a2width(const char *p, struct roffsu *su)
1044: {
1.83 schwarze 1045: const char *end;
1046:
1047: end = a2roffsu(p, su, SCALE_MAX);
1048: if (end == NULL || *end != '\0') {
1.64 schwarze 1049: su->unit = SCALE_EN;
1050: su->scale = html_strlen(p);
1051: } else if (su->scale < 0.0)
1052: su->scale = 0.0;
1.3 schwarze 1053: }