1*6d38604fSBaptiste Daroussin /* $Id: html.c,v 1.275 2021/09/09 14:47:24 schwarze Exp $ */ 261d06d6bSBaptiste Daroussin /* 361d06d6bSBaptiste Daroussin * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4*6d38604fSBaptiste Daroussin * Copyright (c) 2011-2015, 2017-2021 Ingo Schwarze <schwarze@openbsd.org> 561d06d6bSBaptiste Daroussin * 661d06d6bSBaptiste Daroussin * Permission to use, copy, modify, and distribute this software for any 761d06d6bSBaptiste Daroussin * purpose with or without fee is hereby granted, provided that the above 861d06d6bSBaptiste Daroussin * copyright notice and this permission notice appear in all copies. 961d06d6bSBaptiste Daroussin * 1061d06d6bSBaptiste Daroussin * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 1161d06d6bSBaptiste Daroussin * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1261d06d6bSBaptiste Daroussin * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 1361d06d6bSBaptiste Daroussin * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1461d06d6bSBaptiste Daroussin * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1561d06d6bSBaptiste Daroussin * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1661d06d6bSBaptiste Daroussin * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17*6d38604fSBaptiste Daroussin * 18*6d38604fSBaptiste Daroussin * Common functions for mandoc(1) HTML formatters. 19*6d38604fSBaptiste Daroussin * For use by individual formatters and by the main program. 2061d06d6bSBaptiste Daroussin */ 2161d06d6bSBaptiste Daroussin #include "config.h" 2261d06d6bSBaptiste Daroussin 2361d06d6bSBaptiste Daroussin #include <sys/types.h> 247295610fSBaptiste Daroussin #include <sys/stat.h> 2561d06d6bSBaptiste Daroussin 2661d06d6bSBaptiste Daroussin #include <assert.h> 2761d06d6bSBaptiste Daroussin #include <ctype.h> 2861d06d6bSBaptiste Daroussin #include <stdarg.h> 2961d06d6bSBaptiste Daroussin #include <stddef.h> 3061d06d6bSBaptiste Daroussin #include <stdio.h> 3161d06d6bSBaptiste Daroussin #include <stdint.h> 3261d06d6bSBaptiste Daroussin #include <stdlib.h> 3361d06d6bSBaptiste Daroussin #include <string.h> 3461d06d6bSBaptiste Daroussin #include <unistd.h> 3561d06d6bSBaptiste Daroussin 3661d06d6bSBaptiste Daroussin #include "mandoc_aux.h" 3761d06d6bSBaptiste Daroussin #include "mandoc_ohash.h" 3861d06d6bSBaptiste Daroussin #include "mandoc.h" 3961d06d6bSBaptiste Daroussin #include "roff.h" 4061d06d6bSBaptiste Daroussin #include "out.h" 4161d06d6bSBaptiste Daroussin #include "html.h" 4261d06d6bSBaptiste Daroussin #include "manconf.h" 4361d06d6bSBaptiste Daroussin #include "main.h" 4461d06d6bSBaptiste Daroussin 4561d06d6bSBaptiste Daroussin struct htmldata { 4661d06d6bSBaptiste Daroussin const char *name; 4761d06d6bSBaptiste Daroussin int flags; 48*6d38604fSBaptiste Daroussin #define HTML_INPHRASE (1 << 0) /* Can appear in phrasing context. */ 49*6d38604fSBaptiste Daroussin #define HTML_TOPHRASE (1 << 1) /* Establishes phrasing context. */ 50*6d38604fSBaptiste Daroussin #define HTML_NOSTACK (1 << 2) /* Does not have an end tag. */ 51*6d38604fSBaptiste Daroussin #define HTML_NLBEFORE (1 << 3) /* Output line break before opening. */ 52*6d38604fSBaptiste Daroussin #define HTML_NLBEGIN (1 << 4) /* Output line break after opening. */ 53*6d38604fSBaptiste Daroussin #define HTML_NLEND (1 << 5) /* Output line break before closing. */ 54*6d38604fSBaptiste Daroussin #define HTML_NLAFTER (1 << 6) /* Output line break after closing. */ 5561d06d6bSBaptiste Daroussin #define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER) 5661d06d6bSBaptiste Daroussin #define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND) 5761d06d6bSBaptiste Daroussin #define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE) 58*6d38604fSBaptiste Daroussin #define HTML_INDENT (1 << 7) /* Indent content by two spaces. */ 59*6d38604fSBaptiste Daroussin #define HTML_NOINDENT (1 << 8) /* Exception: never indent content. */ 6061d06d6bSBaptiste Daroussin }; 6161d06d6bSBaptiste Daroussin 6261d06d6bSBaptiste Daroussin static const struct htmldata htmltags[TAG_MAX] = { 6361d06d6bSBaptiste Daroussin {"html", HTML_NLALL}, 6461d06d6bSBaptiste Daroussin {"head", HTML_NLALL | HTML_INDENT}, 65*6d38604fSBaptiste Daroussin {"meta", HTML_NOSTACK | HTML_NLALL}, 66*6d38604fSBaptiste Daroussin {"link", HTML_NOSTACK | HTML_NLALL}, 67*6d38604fSBaptiste Daroussin {"style", HTML_NLALL | HTML_INDENT}, 6861d06d6bSBaptiste Daroussin {"title", HTML_NLAROUND}, 69*6d38604fSBaptiste Daroussin {"body", HTML_NLALL}, 7061d06d6bSBaptiste Daroussin {"div", HTML_NLAROUND}, 717295610fSBaptiste Daroussin {"section", HTML_NLALL}, 7261d06d6bSBaptiste Daroussin {"table", HTML_NLALL | HTML_INDENT}, 7361d06d6bSBaptiste Daroussin {"tr", HTML_NLALL | HTML_INDENT}, 7461d06d6bSBaptiste Daroussin {"td", HTML_NLAROUND}, 7561d06d6bSBaptiste Daroussin {"li", HTML_NLAROUND | HTML_INDENT}, 7661d06d6bSBaptiste Daroussin {"ul", HTML_NLALL | HTML_INDENT}, 7761d06d6bSBaptiste Daroussin {"ol", HTML_NLALL | HTML_INDENT}, 7861d06d6bSBaptiste Daroussin {"dl", HTML_NLALL | HTML_INDENT}, 7961d06d6bSBaptiste Daroussin {"dt", HTML_NLAROUND}, 8061d06d6bSBaptiste Daroussin {"dd", HTML_NLAROUND | HTML_INDENT}, 81*6d38604fSBaptiste Daroussin {"h1", HTML_TOPHRASE | HTML_NLAROUND}, 82*6d38604fSBaptiste Daroussin {"h2", HTML_TOPHRASE | HTML_NLAROUND}, 83*6d38604fSBaptiste Daroussin {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT}, 84*6d38604fSBaptiste Daroussin {"pre", HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT}, 85*6d38604fSBaptiste Daroussin {"a", HTML_INPHRASE | HTML_TOPHRASE}, 86*6d38604fSBaptiste Daroussin {"b", HTML_INPHRASE | HTML_TOPHRASE}, 87*6d38604fSBaptiste Daroussin {"cite", HTML_INPHRASE | HTML_TOPHRASE}, 88*6d38604fSBaptiste Daroussin {"code", HTML_INPHRASE | HTML_TOPHRASE}, 89*6d38604fSBaptiste Daroussin {"i", HTML_INPHRASE | HTML_TOPHRASE}, 90*6d38604fSBaptiste Daroussin {"small", HTML_INPHRASE | HTML_TOPHRASE}, 91*6d38604fSBaptiste Daroussin {"span", HTML_INPHRASE | HTML_TOPHRASE}, 92*6d38604fSBaptiste Daroussin {"var", HTML_INPHRASE | HTML_TOPHRASE}, 93*6d38604fSBaptiste Daroussin {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL}, 94*6d38604fSBaptiste Daroussin {"hr", HTML_INPHRASE | HTML_NOSTACK}, 95*6d38604fSBaptiste Daroussin {"mark", HTML_INPHRASE }, 96*6d38604fSBaptiste Daroussin {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT}, 9761d06d6bSBaptiste Daroussin {"mrow", 0}, 9861d06d6bSBaptiste Daroussin {"mi", 0}, 9961d06d6bSBaptiste Daroussin {"mn", 0}, 10061d06d6bSBaptiste Daroussin {"mo", 0}, 10161d06d6bSBaptiste Daroussin {"msup", 0}, 10261d06d6bSBaptiste Daroussin {"msub", 0}, 10361d06d6bSBaptiste Daroussin {"msubsup", 0}, 10461d06d6bSBaptiste Daroussin {"mfrac", 0}, 10561d06d6bSBaptiste Daroussin {"msqrt", 0}, 10661d06d6bSBaptiste Daroussin {"mfenced", 0}, 10761d06d6bSBaptiste Daroussin {"mtable", 0}, 10861d06d6bSBaptiste Daroussin {"mtr", 0}, 10961d06d6bSBaptiste Daroussin {"mtd", 0}, 11061d06d6bSBaptiste Daroussin {"munderover", 0}, 11161d06d6bSBaptiste Daroussin {"munder", 0}, 11261d06d6bSBaptiste Daroussin {"mover", 0}, 11361d06d6bSBaptiste Daroussin }; 11461d06d6bSBaptiste Daroussin 11561d06d6bSBaptiste Daroussin /* Avoid duplicate HTML id= attributes. */ 116*6d38604fSBaptiste Daroussin 117*6d38604fSBaptiste Daroussin struct id_entry { 118*6d38604fSBaptiste Daroussin int ord; /* Ordinal number of the latest occurrence. */ 119*6d38604fSBaptiste Daroussin char id[]; /* The id= attribute without any ordinal suffix. */ 120*6d38604fSBaptiste Daroussin }; 12161d06d6bSBaptiste Daroussin static struct ohash id_unique; 12261d06d6bSBaptiste Daroussin 1237295610fSBaptiste Daroussin static void html_reset_internal(struct html *); 12461d06d6bSBaptiste Daroussin static void print_byte(struct html *, char); 12561d06d6bSBaptiste Daroussin static void print_endword(struct html *); 12661d06d6bSBaptiste Daroussin static void print_indent(struct html *); 12761d06d6bSBaptiste Daroussin static void print_word(struct html *, const char *); 12861d06d6bSBaptiste Daroussin 12961d06d6bSBaptiste Daroussin static void print_ctag(struct html *, struct tag *); 13061d06d6bSBaptiste Daroussin static int print_escape(struct html *, char); 13161d06d6bSBaptiste Daroussin static int print_encode(struct html *, const char *, const char *, int); 13261d06d6bSBaptiste Daroussin static void print_href(struct html *, const char *, const char *, int); 13345a5aec3SBaptiste Daroussin static void print_metaf(struct html *); 13461d06d6bSBaptiste Daroussin 13561d06d6bSBaptiste Daroussin 13661d06d6bSBaptiste Daroussin void * 13761d06d6bSBaptiste Daroussin html_alloc(const struct manoutput *outopts) 13861d06d6bSBaptiste Daroussin { 13961d06d6bSBaptiste Daroussin struct html *h; 14061d06d6bSBaptiste Daroussin 14161d06d6bSBaptiste Daroussin h = mandoc_calloc(1, sizeof(struct html)); 14261d06d6bSBaptiste Daroussin 14361d06d6bSBaptiste Daroussin h->tag = NULL; 144*6d38604fSBaptiste Daroussin h->metac = h->metal = ESCAPE_FONTROMAN; 14561d06d6bSBaptiste Daroussin h->style = outopts->style; 1467295610fSBaptiste Daroussin if ((h->base_man1 = outopts->man) == NULL) 1477295610fSBaptiste Daroussin h->base_man2 = NULL; 1487295610fSBaptiste Daroussin else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL) 1497295610fSBaptiste Daroussin *h->base_man2++ = '\0'; 15061d06d6bSBaptiste Daroussin h->base_includes = outopts->includes; 15161d06d6bSBaptiste Daroussin if (outopts->fragment) 15261d06d6bSBaptiste Daroussin h->oflags |= HTML_FRAGMENT; 1537295610fSBaptiste Daroussin if (outopts->toc) 1547295610fSBaptiste Daroussin h->oflags |= HTML_TOC; 15561d06d6bSBaptiste Daroussin 156*6d38604fSBaptiste Daroussin mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); 15761d06d6bSBaptiste Daroussin 15861d06d6bSBaptiste Daroussin return h; 15961d06d6bSBaptiste Daroussin } 16061d06d6bSBaptiste Daroussin 1617295610fSBaptiste Daroussin static void 1627295610fSBaptiste Daroussin html_reset_internal(struct html *h) 16361d06d6bSBaptiste Daroussin { 16461d06d6bSBaptiste Daroussin struct tag *tag; 165*6d38604fSBaptiste Daroussin struct id_entry *entry; 16661d06d6bSBaptiste Daroussin unsigned int slot; 16761d06d6bSBaptiste Daroussin 16861d06d6bSBaptiste Daroussin while ((tag = h->tag) != NULL) { 16961d06d6bSBaptiste Daroussin h->tag = tag->next; 17061d06d6bSBaptiste Daroussin free(tag); 17161d06d6bSBaptiste Daroussin } 172*6d38604fSBaptiste Daroussin entry = ohash_first(&id_unique, &slot); 173*6d38604fSBaptiste Daroussin while (entry != NULL) { 174*6d38604fSBaptiste Daroussin free(entry); 175*6d38604fSBaptiste Daroussin entry = ohash_next(&id_unique, &slot); 17661d06d6bSBaptiste Daroussin } 17761d06d6bSBaptiste Daroussin ohash_delete(&id_unique); 17861d06d6bSBaptiste Daroussin } 17961d06d6bSBaptiste Daroussin 18061d06d6bSBaptiste Daroussin void 1817295610fSBaptiste Daroussin html_reset(void *p) 1827295610fSBaptiste Daroussin { 1837295610fSBaptiste Daroussin html_reset_internal(p); 184*6d38604fSBaptiste Daroussin mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); 1857295610fSBaptiste Daroussin } 1867295610fSBaptiste Daroussin 1877295610fSBaptiste Daroussin void 1887295610fSBaptiste Daroussin html_free(void *p) 1897295610fSBaptiste Daroussin { 1907295610fSBaptiste Daroussin html_reset_internal(p); 1917295610fSBaptiste Daroussin free(p); 1927295610fSBaptiste Daroussin } 1937295610fSBaptiste Daroussin 1947295610fSBaptiste Daroussin void 19561d06d6bSBaptiste Daroussin print_gen_head(struct html *h) 19661d06d6bSBaptiste Daroussin { 19761d06d6bSBaptiste Daroussin struct tag *t; 19861d06d6bSBaptiste Daroussin 19961d06d6bSBaptiste Daroussin print_otag(h, TAG_META, "?", "charset", "utf-8"); 200*6d38604fSBaptiste Daroussin print_otag(h, TAG_META, "??", "name", "viewport", 201*6d38604fSBaptiste Daroussin "content", "width=device-width, initial-scale=1.0"); 20261d06d6bSBaptiste Daroussin if (h->style != NULL) { 20361d06d6bSBaptiste Daroussin print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", 20461d06d6bSBaptiste Daroussin h->style, "type", "text/css", "media", "all"); 20561d06d6bSBaptiste Daroussin return; 20661d06d6bSBaptiste Daroussin } 20761d06d6bSBaptiste Daroussin 20861d06d6bSBaptiste Daroussin /* 20961d06d6bSBaptiste Daroussin * Print a minimal embedded style sheet. 21061d06d6bSBaptiste Daroussin */ 21161d06d6bSBaptiste Daroussin 21261d06d6bSBaptiste Daroussin t = print_otag(h, TAG_STYLE, ""); 21361d06d6bSBaptiste Daroussin print_text(h, "table.head, table.foot { width: 100%; }"); 21461d06d6bSBaptiste Daroussin print_endline(h); 21561d06d6bSBaptiste Daroussin print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }"); 21661d06d6bSBaptiste Daroussin print_endline(h); 21761d06d6bSBaptiste Daroussin print_text(h, "td.head-vol { text-align: center; }"); 21861d06d6bSBaptiste Daroussin print_endline(h); 219*6d38604fSBaptiste Daroussin print_text(h, ".Nd, .Bf, .Op { display: inline; }"); 22061d06d6bSBaptiste Daroussin print_endline(h); 221*6d38604fSBaptiste Daroussin print_text(h, ".Pa, .Ad { font-style: italic; }"); 22261d06d6bSBaptiste Daroussin print_endline(h); 223*6d38604fSBaptiste Daroussin print_text(h, ".Ms { font-weight: bold; }"); 22461d06d6bSBaptiste Daroussin print_endline(h); 225*6d38604fSBaptiste Daroussin print_text(h, ".Bl-diag "); 22661d06d6bSBaptiste Daroussin print_byte(h, '>'); 22761d06d6bSBaptiste Daroussin print_text(h, " dt { font-weight: bold; }"); 22861d06d6bSBaptiste Daroussin print_endline(h); 229*6d38604fSBaptiste Daroussin print_text(h, "code.Nm, .Fl, .Cm, .Ic, code.In, .Fd, .Fn, .Cd " 230*6d38604fSBaptiste Daroussin "{ font-weight: bold; font-family: inherit; }"); 23161d06d6bSBaptiste Daroussin print_tagq(h, t); 23261d06d6bSBaptiste Daroussin } 23361d06d6bSBaptiste Daroussin 23445a5aec3SBaptiste Daroussin int 23545a5aec3SBaptiste Daroussin html_setfont(struct html *h, enum mandoc_esc font) 23661d06d6bSBaptiste Daroussin { 23745a5aec3SBaptiste Daroussin switch (font) { 23861d06d6bSBaptiste Daroussin case ESCAPE_FONTPREV: 23961d06d6bSBaptiste Daroussin font = h->metal; 24061d06d6bSBaptiste Daroussin break; 24161d06d6bSBaptiste Daroussin case ESCAPE_FONTITALIC: 24261d06d6bSBaptiste Daroussin case ESCAPE_FONTBOLD: 24361d06d6bSBaptiste Daroussin case ESCAPE_FONTBI: 24445a5aec3SBaptiste Daroussin case ESCAPE_FONTROMAN: 245*6d38604fSBaptiste Daroussin case ESCAPE_FONTCR: 246*6d38604fSBaptiste Daroussin case ESCAPE_FONTCB: 247*6d38604fSBaptiste Daroussin case ESCAPE_FONTCI: 2487295610fSBaptiste Daroussin break; 24961d06d6bSBaptiste Daroussin case ESCAPE_FONT: 25045a5aec3SBaptiste Daroussin font = ESCAPE_FONTROMAN; 25161d06d6bSBaptiste Daroussin break; 25261d06d6bSBaptiste Daroussin default: 25345a5aec3SBaptiste Daroussin return 0; 25445a5aec3SBaptiste Daroussin } 25545a5aec3SBaptiste Daroussin h->metal = h->metac; 25645a5aec3SBaptiste Daroussin h->metac = font; 25745a5aec3SBaptiste Daroussin return 1; 25861d06d6bSBaptiste Daroussin } 25961d06d6bSBaptiste Daroussin 26045a5aec3SBaptiste Daroussin static void 26145a5aec3SBaptiste Daroussin print_metaf(struct html *h) 26245a5aec3SBaptiste Daroussin { 26361d06d6bSBaptiste Daroussin if (h->metaf) { 26461d06d6bSBaptiste Daroussin print_tagq(h, h->metaf); 26561d06d6bSBaptiste Daroussin h->metaf = NULL; 26661d06d6bSBaptiste Daroussin } 26745a5aec3SBaptiste Daroussin switch (h->metac) { 26845a5aec3SBaptiste Daroussin case ESCAPE_FONTITALIC: 26961d06d6bSBaptiste Daroussin h->metaf = print_otag(h, TAG_I, ""); 27061d06d6bSBaptiste Daroussin break; 27145a5aec3SBaptiste Daroussin case ESCAPE_FONTBOLD: 27261d06d6bSBaptiste Daroussin h->metaf = print_otag(h, TAG_B, ""); 27361d06d6bSBaptiste Daroussin break; 27445a5aec3SBaptiste Daroussin case ESCAPE_FONTBI: 27561d06d6bSBaptiste Daroussin h->metaf = print_otag(h, TAG_B, ""); 27661d06d6bSBaptiste Daroussin print_otag(h, TAG_I, ""); 27761d06d6bSBaptiste Daroussin break; 278*6d38604fSBaptiste Daroussin case ESCAPE_FONTCR: 2797295610fSBaptiste Daroussin h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); 2807295610fSBaptiste Daroussin break; 281*6d38604fSBaptiste Daroussin case ESCAPE_FONTCB: 282*6d38604fSBaptiste Daroussin h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); 283*6d38604fSBaptiste Daroussin print_otag(h, TAG_B, ""); 284*6d38604fSBaptiste Daroussin break; 285*6d38604fSBaptiste Daroussin case ESCAPE_FONTCI: 286*6d38604fSBaptiste Daroussin h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); 287*6d38604fSBaptiste Daroussin print_otag(h, TAG_I, ""); 288*6d38604fSBaptiste Daroussin break; 28961d06d6bSBaptiste Daroussin default: 29061d06d6bSBaptiste Daroussin break; 29161d06d6bSBaptiste Daroussin } 29261d06d6bSBaptiste Daroussin } 29361d06d6bSBaptiste Daroussin 2947295610fSBaptiste Daroussin void 2957295610fSBaptiste Daroussin html_close_paragraph(struct html *h) 2967295610fSBaptiste Daroussin { 297*6d38604fSBaptiste Daroussin struct tag *this, *next; 298*6d38604fSBaptiste Daroussin int flags; 2997295610fSBaptiste Daroussin 300*6d38604fSBaptiste Daroussin this = h->tag; 301*6d38604fSBaptiste Daroussin for (;;) { 302*6d38604fSBaptiste Daroussin next = this->next; 303*6d38604fSBaptiste Daroussin flags = htmltags[this->tag].flags; 304*6d38604fSBaptiste Daroussin if (flags & (HTML_INPHRASE | HTML_TOPHRASE)) 305*6d38604fSBaptiste Daroussin print_ctag(h, this); 306*6d38604fSBaptiste Daroussin if ((flags & HTML_INPHRASE) == 0) 3077295610fSBaptiste Daroussin break; 308*6d38604fSBaptiste Daroussin this = next; 3097295610fSBaptiste Daroussin } 3107295610fSBaptiste Daroussin } 3117295610fSBaptiste Daroussin 3127295610fSBaptiste Daroussin /* 3137295610fSBaptiste Daroussin * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode. 3147295610fSBaptiste Daroussin * TOKEN_NONE does not switch. The old mode is returned. 3157295610fSBaptiste Daroussin */ 3167295610fSBaptiste Daroussin enum roff_tok 3177295610fSBaptiste Daroussin html_fillmode(struct html *h, enum roff_tok want) 3187295610fSBaptiste Daroussin { 3197295610fSBaptiste Daroussin struct tag *t; 3207295610fSBaptiste Daroussin enum roff_tok had; 3217295610fSBaptiste Daroussin 3227295610fSBaptiste Daroussin for (t = h->tag; t != NULL; t = t->next) 3237295610fSBaptiste Daroussin if (t->tag == TAG_PRE) 3247295610fSBaptiste Daroussin break; 3257295610fSBaptiste Daroussin 3267295610fSBaptiste Daroussin had = t == NULL ? ROFF_fi : ROFF_nf; 3277295610fSBaptiste Daroussin 3287295610fSBaptiste Daroussin if (want != had) { 3297295610fSBaptiste Daroussin switch (want) { 3307295610fSBaptiste Daroussin case ROFF_fi: 3317295610fSBaptiste Daroussin print_tagq(h, t); 3327295610fSBaptiste Daroussin break; 3337295610fSBaptiste Daroussin case ROFF_nf: 3347295610fSBaptiste Daroussin html_close_paragraph(h); 3357295610fSBaptiste Daroussin print_otag(h, TAG_PRE, ""); 3367295610fSBaptiste Daroussin break; 3377295610fSBaptiste Daroussin case TOKEN_NONE: 3387295610fSBaptiste Daroussin break; 3397295610fSBaptiste Daroussin default: 3407295610fSBaptiste Daroussin abort(); 3417295610fSBaptiste Daroussin } 3427295610fSBaptiste Daroussin } 3437295610fSBaptiste Daroussin return had; 3447295610fSBaptiste Daroussin } 3457295610fSBaptiste Daroussin 346*6d38604fSBaptiste Daroussin /* 347*6d38604fSBaptiste Daroussin * Allocate a string to be used for the "id=" attribute of an HTML 348*6d38604fSBaptiste Daroussin * element and/or as a segment identifier for a URI in an <a> element. 349*6d38604fSBaptiste Daroussin * The function may fail and return NULL if the node lacks text data 350*6d38604fSBaptiste Daroussin * to create the attribute from. 351*6d38604fSBaptiste Daroussin * The caller is responsible for free(3)ing the returned string. 352*6d38604fSBaptiste Daroussin * 353*6d38604fSBaptiste Daroussin * If the "unique" argument is non-zero, the "id_unique" ohash table 354*6d38604fSBaptiste Daroussin * is used for de-duplication. If the "unique" argument is 1, 355*6d38604fSBaptiste Daroussin * it is the first time the function is called for this tag and 356*6d38604fSBaptiste Daroussin * location, so if an ordinal suffix is needed, it is incremented. 357*6d38604fSBaptiste Daroussin * If the "unique" argument is 2, it is the second time the function 358*6d38604fSBaptiste Daroussin * is called for this tag and location, so the ordinal suffix 359*6d38604fSBaptiste Daroussin * remains unchanged. 360*6d38604fSBaptiste Daroussin */ 36161d06d6bSBaptiste Daroussin char * 36261d06d6bSBaptiste Daroussin html_make_id(const struct roff_node *n, int unique) 36361d06d6bSBaptiste Daroussin { 36461d06d6bSBaptiste Daroussin const struct roff_node *nch; 365*6d38604fSBaptiste Daroussin struct id_entry *entry; 366*6d38604fSBaptiste Daroussin char *buf, *cp; 367*6d38604fSBaptiste Daroussin size_t len; 36861d06d6bSBaptiste Daroussin unsigned int slot; 36961d06d6bSBaptiste Daroussin 370*6d38604fSBaptiste Daroussin if (n->tag != NULL) 371*6d38604fSBaptiste Daroussin buf = mandoc_strdup(n->tag); 372*6d38604fSBaptiste Daroussin else { 373*6d38604fSBaptiste Daroussin switch (n->tok) { 374*6d38604fSBaptiste Daroussin case MDOC_Sh: 375*6d38604fSBaptiste Daroussin case MDOC_Ss: 376*6d38604fSBaptiste Daroussin case MDOC_Sx: 377*6d38604fSBaptiste Daroussin case MAN_SH: 378*6d38604fSBaptiste Daroussin case MAN_SS: 37961d06d6bSBaptiste Daroussin for (nch = n->child; nch != NULL; nch = nch->next) 38061d06d6bSBaptiste Daroussin if (nch->type != ROFFT_TEXT) 38161d06d6bSBaptiste Daroussin return NULL; 38261d06d6bSBaptiste Daroussin buf = NULL; 38361d06d6bSBaptiste Daroussin deroff(&buf, n); 38461d06d6bSBaptiste Daroussin if (buf == NULL) 38561d06d6bSBaptiste Daroussin return NULL; 386*6d38604fSBaptiste Daroussin break; 387*6d38604fSBaptiste Daroussin default: 388*6d38604fSBaptiste Daroussin if (n->child == NULL || n->child->type != ROFFT_TEXT) 389*6d38604fSBaptiste Daroussin return NULL; 390*6d38604fSBaptiste Daroussin buf = mandoc_strdup(n->child->string); 391*6d38604fSBaptiste Daroussin break; 392*6d38604fSBaptiste Daroussin } 393*6d38604fSBaptiste Daroussin } 39461d06d6bSBaptiste Daroussin 39561d06d6bSBaptiste Daroussin /* 39661d06d6bSBaptiste Daroussin * In ID attributes, only use ASCII characters that are 39761d06d6bSBaptiste Daroussin * permitted in URL-fragment strings according to the 39861d06d6bSBaptiste Daroussin * explicit list at: 39961d06d6bSBaptiste Daroussin * https://url.spec.whatwg.org/#url-fragment-string 400*6d38604fSBaptiste Daroussin * In addition, reserve '~' for ordinal suffixes. 40161d06d6bSBaptiste Daroussin */ 40261d06d6bSBaptiste Daroussin 40361d06d6bSBaptiste Daroussin for (cp = buf; *cp != '\0'; cp++) 40461d06d6bSBaptiste Daroussin if (isalnum((unsigned char)*cp) == 0 && 405*6d38604fSBaptiste Daroussin strchr("!$&'()*+,-./:;=?@_", *cp) == NULL) 40661d06d6bSBaptiste Daroussin *cp = '_'; 40761d06d6bSBaptiste Daroussin 40861d06d6bSBaptiste Daroussin if (unique == 0) 40961d06d6bSBaptiste Daroussin return buf; 41061d06d6bSBaptiste Daroussin 41161d06d6bSBaptiste Daroussin /* Avoid duplicate HTML id= attributes. */ 41261d06d6bSBaptiste Daroussin 41361d06d6bSBaptiste Daroussin slot = ohash_qlookup(&id_unique, buf); 414*6d38604fSBaptiste Daroussin if ((entry = ohash_find(&id_unique, slot)) == NULL) { 415*6d38604fSBaptiste Daroussin len = strlen(buf) + 1; 416*6d38604fSBaptiste Daroussin entry = mandoc_malloc(sizeof(*entry) + len); 417*6d38604fSBaptiste Daroussin entry->ord = 1; 418*6d38604fSBaptiste Daroussin memcpy(entry->id, buf, len); 419*6d38604fSBaptiste Daroussin ohash_insert(&id_unique, slot, entry); 420*6d38604fSBaptiste Daroussin } else if (unique == 1) 421*6d38604fSBaptiste Daroussin entry->ord++; 422*6d38604fSBaptiste Daroussin 423*6d38604fSBaptiste Daroussin if (entry->ord > 1) { 424*6d38604fSBaptiste Daroussin cp = buf; 425*6d38604fSBaptiste Daroussin mandoc_asprintf(&buf, "%s~%d", cp, entry->ord); 426*6d38604fSBaptiste Daroussin free(cp); 42761d06d6bSBaptiste Daroussin } 42861d06d6bSBaptiste Daroussin return buf; 42961d06d6bSBaptiste Daroussin } 43061d06d6bSBaptiste Daroussin 43161d06d6bSBaptiste Daroussin static int 43261d06d6bSBaptiste Daroussin print_escape(struct html *h, char c) 43361d06d6bSBaptiste Daroussin { 43461d06d6bSBaptiste Daroussin 43561d06d6bSBaptiste Daroussin switch (c) { 43661d06d6bSBaptiste Daroussin case '<': 43761d06d6bSBaptiste Daroussin print_word(h, "<"); 43861d06d6bSBaptiste Daroussin break; 43961d06d6bSBaptiste Daroussin case '>': 44061d06d6bSBaptiste Daroussin print_word(h, ">"); 44161d06d6bSBaptiste Daroussin break; 44261d06d6bSBaptiste Daroussin case '&': 44361d06d6bSBaptiste Daroussin print_word(h, "&"); 44461d06d6bSBaptiste Daroussin break; 44561d06d6bSBaptiste Daroussin case '"': 44661d06d6bSBaptiste Daroussin print_word(h, """); 44761d06d6bSBaptiste Daroussin break; 44861d06d6bSBaptiste Daroussin case ASCII_NBRSP: 44961d06d6bSBaptiste Daroussin print_word(h, " "); 45061d06d6bSBaptiste Daroussin break; 45161d06d6bSBaptiste Daroussin case ASCII_HYPH: 45261d06d6bSBaptiste Daroussin print_byte(h, '-'); 45361d06d6bSBaptiste Daroussin break; 45461d06d6bSBaptiste Daroussin case ASCII_BREAK: 45561d06d6bSBaptiste Daroussin break; 45661d06d6bSBaptiste Daroussin default: 45761d06d6bSBaptiste Daroussin return 0; 45861d06d6bSBaptiste Daroussin } 45961d06d6bSBaptiste Daroussin return 1; 46061d06d6bSBaptiste Daroussin } 46161d06d6bSBaptiste Daroussin 46261d06d6bSBaptiste Daroussin static int 46361d06d6bSBaptiste Daroussin print_encode(struct html *h, const char *p, const char *pend, int norecurse) 46461d06d6bSBaptiste Daroussin { 46561d06d6bSBaptiste Daroussin char numbuf[16]; 46661d06d6bSBaptiste Daroussin const char *seq; 46761d06d6bSBaptiste Daroussin size_t sz; 46861d06d6bSBaptiste Daroussin int c, len, breakline, nospace; 46961d06d6bSBaptiste Daroussin enum mandoc_esc esc; 47061d06d6bSBaptiste Daroussin static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"', 47161d06d6bSBaptiste Daroussin ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; 47261d06d6bSBaptiste Daroussin 47361d06d6bSBaptiste Daroussin if (pend == NULL) 47461d06d6bSBaptiste Daroussin pend = strchr(p, '\0'); 47561d06d6bSBaptiste Daroussin 47661d06d6bSBaptiste Daroussin breakline = 0; 47761d06d6bSBaptiste Daroussin nospace = 0; 47861d06d6bSBaptiste Daroussin 47961d06d6bSBaptiste Daroussin while (p < pend) { 48061d06d6bSBaptiste Daroussin if (HTML_SKIPCHAR & h->flags && '\\' != *p) { 48161d06d6bSBaptiste Daroussin h->flags &= ~HTML_SKIPCHAR; 48261d06d6bSBaptiste Daroussin p++; 48361d06d6bSBaptiste Daroussin continue; 48461d06d6bSBaptiste Daroussin } 48561d06d6bSBaptiste Daroussin 48661d06d6bSBaptiste Daroussin for (sz = strcspn(p, rejs); sz-- && p < pend; p++) 48761d06d6bSBaptiste Daroussin print_byte(h, *p); 48861d06d6bSBaptiste Daroussin 48961d06d6bSBaptiste Daroussin if (breakline && 49061d06d6bSBaptiste Daroussin (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) { 4917295610fSBaptiste Daroussin print_otag(h, TAG_BR, ""); 49261d06d6bSBaptiste Daroussin breakline = 0; 49361d06d6bSBaptiste Daroussin while (p < pend && (*p == ' ' || *p == ASCII_NBRSP)) 49461d06d6bSBaptiste Daroussin p++; 49561d06d6bSBaptiste Daroussin continue; 49661d06d6bSBaptiste Daroussin } 49761d06d6bSBaptiste Daroussin 49861d06d6bSBaptiste Daroussin if (p >= pend) 49961d06d6bSBaptiste Daroussin break; 50061d06d6bSBaptiste Daroussin 50161d06d6bSBaptiste Daroussin if (*p == ' ') { 50261d06d6bSBaptiste Daroussin print_endword(h); 50361d06d6bSBaptiste Daroussin p++; 50461d06d6bSBaptiste Daroussin continue; 50561d06d6bSBaptiste Daroussin } 50661d06d6bSBaptiste Daroussin 50761d06d6bSBaptiste Daroussin if (print_escape(h, *p++)) 50861d06d6bSBaptiste Daroussin continue; 50961d06d6bSBaptiste Daroussin 51061d06d6bSBaptiste Daroussin esc = mandoc_escape(&p, &seq, &len); 51161d06d6bSBaptiste Daroussin switch (esc) { 51261d06d6bSBaptiste Daroussin case ESCAPE_FONT: 51361d06d6bSBaptiste Daroussin case ESCAPE_FONTPREV: 51461d06d6bSBaptiste Daroussin case ESCAPE_FONTBOLD: 51561d06d6bSBaptiste Daroussin case ESCAPE_FONTITALIC: 51661d06d6bSBaptiste Daroussin case ESCAPE_FONTBI: 51761d06d6bSBaptiste Daroussin case ESCAPE_FONTROMAN: 518*6d38604fSBaptiste Daroussin case ESCAPE_FONTCR: 519*6d38604fSBaptiste Daroussin case ESCAPE_FONTCB: 520*6d38604fSBaptiste Daroussin case ESCAPE_FONTCI: 5217295610fSBaptiste Daroussin if (0 == norecurse) { 5227295610fSBaptiste Daroussin h->flags |= HTML_NOSPACE; 52345a5aec3SBaptiste Daroussin if (html_setfont(h, esc)) 52445a5aec3SBaptiste Daroussin print_metaf(h); 5257295610fSBaptiste Daroussin h->flags &= ~HTML_NOSPACE; 5267295610fSBaptiste Daroussin } 52761d06d6bSBaptiste Daroussin continue; 52861d06d6bSBaptiste Daroussin case ESCAPE_SKIPCHAR: 52961d06d6bSBaptiste Daroussin h->flags |= HTML_SKIPCHAR; 53061d06d6bSBaptiste Daroussin continue; 5317295610fSBaptiste Daroussin case ESCAPE_ERROR: 5327295610fSBaptiste Daroussin continue; 53361d06d6bSBaptiste Daroussin default: 53461d06d6bSBaptiste Daroussin break; 53561d06d6bSBaptiste Daroussin } 53661d06d6bSBaptiste Daroussin 53761d06d6bSBaptiste Daroussin if (h->flags & HTML_SKIPCHAR) { 53861d06d6bSBaptiste Daroussin h->flags &= ~HTML_SKIPCHAR; 53961d06d6bSBaptiste Daroussin continue; 54061d06d6bSBaptiste Daroussin } 54161d06d6bSBaptiste Daroussin 54261d06d6bSBaptiste Daroussin switch (esc) { 54361d06d6bSBaptiste Daroussin case ESCAPE_UNICODE: 54461d06d6bSBaptiste Daroussin /* Skip past "u" header. */ 54561d06d6bSBaptiste Daroussin c = mchars_num2uc(seq + 1, len - 1); 54661d06d6bSBaptiste Daroussin break; 54761d06d6bSBaptiste Daroussin case ESCAPE_NUMBERED: 54861d06d6bSBaptiste Daroussin c = mchars_num2char(seq, len); 54961d06d6bSBaptiste Daroussin if (c < 0) 55061d06d6bSBaptiste Daroussin continue; 55161d06d6bSBaptiste Daroussin break; 55261d06d6bSBaptiste Daroussin case ESCAPE_SPECIAL: 55361d06d6bSBaptiste Daroussin c = mchars_spec2cp(seq, len); 55461d06d6bSBaptiste Daroussin if (c <= 0) 55561d06d6bSBaptiste Daroussin continue; 55661d06d6bSBaptiste Daroussin break; 5577295610fSBaptiste Daroussin case ESCAPE_UNDEF: 5587295610fSBaptiste Daroussin c = *seq; 5597295610fSBaptiste Daroussin break; 5607295610fSBaptiste Daroussin case ESCAPE_DEVICE: 5617295610fSBaptiste Daroussin print_word(h, "html"); 5627295610fSBaptiste Daroussin continue; 56361d06d6bSBaptiste Daroussin case ESCAPE_BREAK: 56461d06d6bSBaptiste Daroussin breakline = 1; 56561d06d6bSBaptiste Daroussin continue; 56661d06d6bSBaptiste Daroussin case ESCAPE_NOSPACE: 56761d06d6bSBaptiste Daroussin if ('\0' == *p) 56861d06d6bSBaptiste Daroussin nospace = 1; 56961d06d6bSBaptiste Daroussin continue; 57061d06d6bSBaptiste Daroussin case ESCAPE_OVERSTRIKE: 57161d06d6bSBaptiste Daroussin if (len == 0) 57261d06d6bSBaptiste Daroussin continue; 57361d06d6bSBaptiste Daroussin c = seq[len - 1]; 57461d06d6bSBaptiste Daroussin break; 57561d06d6bSBaptiste Daroussin default: 57661d06d6bSBaptiste Daroussin continue; 57761d06d6bSBaptiste Daroussin } 57861d06d6bSBaptiste Daroussin if ((c < 0x20 && c != 0x09) || 57961d06d6bSBaptiste Daroussin (c > 0x7E && c < 0xA0)) 58061d06d6bSBaptiste Daroussin c = 0xFFFD; 58161d06d6bSBaptiste Daroussin if (c > 0x7E) { 58261d06d6bSBaptiste Daroussin (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c); 58361d06d6bSBaptiste Daroussin print_word(h, numbuf); 58461d06d6bSBaptiste Daroussin } else if (print_escape(h, c) == 0) 58561d06d6bSBaptiste Daroussin print_byte(h, c); 58661d06d6bSBaptiste Daroussin } 58761d06d6bSBaptiste Daroussin 58861d06d6bSBaptiste Daroussin return nospace; 58961d06d6bSBaptiste Daroussin } 59061d06d6bSBaptiste Daroussin 59161d06d6bSBaptiste Daroussin static void 59261d06d6bSBaptiste Daroussin print_href(struct html *h, const char *name, const char *sec, int man) 59361d06d6bSBaptiste Daroussin { 5947295610fSBaptiste Daroussin struct stat sb; 59561d06d6bSBaptiste Daroussin const char *p, *pp; 5967295610fSBaptiste Daroussin char *filename; 59761d06d6bSBaptiste Daroussin 5987295610fSBaptiste Daroussin if (man) { 5997295610fSBaptiste Daroussin pp = h->base_man1; 6007295610fSBaptiste Daroussin if (h->base_man2 != NULL) { 6017295610fSBaptiste Daroussin mandoc_asprintf(&filename, "%s.%s", name, sec); 6027295610fSBaptiste Daroussin if (stat(filename, &sb) == -1) 6037295610fSBaptiste Daroussin pp = h->base_man2; 6047295610fSBaptiste Daroussin free(filename); 6057295610fSBaptiste Daroussin } 6067295610fSBaptiste Daroussin } else 6077295610fSBaptiste Daroussin pp = h->base_includes; 6087295610fSBaptiste Daroussin 60961d06d6bSBaptiste Daroussin while ((p = strchr(pp, '%')) != NULL) { 61061d06d6bSBaptiste Daroussin print_encode(h, pp, p, 1); 61161d06d6bSBaptiste Daroussin if (man && p[1] == 'S') { 61261d06d6bSBaptiste Daroussin if (sec == NULL) 61361d06d6bSBaptiste Daroussin print_byte(h, '1'); 61461d06d6bSBaptiste Daroussin else 61561d06d6bSBaptiste Daroussin print_encode(h, sec, NULL, 1); 61661d06d6bSBaptiste Daroussin } else if ((man && p[1] == 'N') || 61761d06d6bSBaptiste Daroussin (man == 0 && p[1] == 'I')) 61861d06d6bSBaptiste Daroussin print_encode(h, name, NULL, 1); 61961d06d6bSBaptiste Daroussin else 62061d06d6bSBaptiste Daroussin print_encode(h, p, p + 2, 1); 62161d06d6bSBaptiste Daroussin pp = p + 2; 62261d06d6bSBaptiste Daroussin } 62361d06d6bSBaptiste Daroussin if (*pp != '\0') 62461d06d6bSBaptiste Daroussin print_encode(h, pp, NULL, 1); 62561d06d6bSBaptiste Daroussin } 62661d06d6bSBaptiste Daroussin 62761d06d6bSBaptiste Daroussin struct tag * 62861d06d6bSBaptiste Daroussin print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) 62961d06d6bSBaptiste Daroussin { 63061d06d6bSBaptiste Daroussin va_list ap; 63161d06d6bSBaptiste Daroussin struct tag *t; 63261d06d6bSBaptiste Daroussin const char *attr; 63361d06d6bSBaptiste Daroussin char *arg1, *arg2; 6347295610fSBaptiste Daroussin int style_written, tflags; 63561d06d6bSBaptiste Daroussin 63661d06d6bSBaptiste Daroussin tflags = htmltags[tag].flags; 63761d06d6bSBaptiste Daroussin 638*6d38604fSBaptiste Daroussin /* Flow content is not allowed in phrasing context. */ 639*6d38604fSBaptiste Daroussin 640*6d38604fSBaptiste Daroussin if ((tflags & HTML_INPHRASE) == 0) { 641*6d38604fSBaptiste Daroussin for (t = h->tag; t != NULL; t = t->next) { 642*6d38604fSBaptiste Daroussin if (t->closed) 643*6d38604fSBaptiste Daroussin continue; 644*6d38604fSBaptiste Daroussin assert((htmltags[t->tag].flags & HTML_TOPHRASE) == 0); 645*6d38604fSBaptiste Daroussin break; 646*6d38604fSBaptiste Daroussin } 647*6d38604fSBaptiste Daroussin 648*6d38604fSBaptiste Daroussin /* 649*6d38604fSBaptiste Daroussin * Always wrap phrasing elements in a paragraph 650*6d38604fSBaptiste Daroussin * unless already contained in some flow container; 651*6d38604fSBaptiste Daroussin * never put them directly into a section. 652*6d38604fSBaptiste Daroussin */ 653*6d38604fSBaptiste Daroussin 654*6d38604fSBaptiste Daroussin } else if (tflags & HTML_TOPHRASE && h->tag->tag == TAG_SECTION) 655*6d38604fSBaptiste Daroussin print_otag(h, TAG_P, "c", "Pp"); 656*6d38604fSBaptiste Daroussin 65761d06d6bSBaptiste Daroussin /* Push this tag onto the stack of open scopes. */ 65861d06d6bSBaptiste Daroussin 65961d06d6bSBaptiste Daroussin if ((tflags & HTML_NOSTACK) == 0) { 66061d06d6bSBaptiste Daroussin t = mandoc_malloc(sizeof(struct tag)); 66161d06d6bSBaptiste Daroussin t->tag = tag; 66261d06d6bSBaptiste Daroussin t->next = h->tag; 6637295610fSBaptiste Daroussin t->refcnt = 0; 6647295610fSBaptiste Daroussin t->closed = 0; 66561d06d6bSBaptiste Daroussin h->tag = t; 66661d06d6bSBaptiste Daroussin } else 66761d06d6bSBaptiste Daroussin t = NULL; 66861d06d6bSBaptiste Daroussin 66961d06d6bSBaptiste Daroussin if (tflags & HTML_NLBEFORE) 67061d06d6bSBaptiste Daroussin print_endline(h); 67161d06d6bSBaptiste Daroussin if (h->col == 0) 67261d06d6bSBaptiste Daroussin print_indent(h); 67361d06d6bSBaptiste Daroussin else if ((h->flags & HTML_NOSPACE) == 0) { 67461d06d6bSBaptiste Daroussin if (h->flags & HTML_KEEP) 67561d06d6bSBaptiste Daroussin print_word(h, " "); 67661d06d6bSBaptiste Daroussin else { 67761d06d6bSBaptiste Daroussin if (h->flags & HTML_PREKEEP) 67861d06d6bSBaptiste Daroussin h->flags |= HTML_KEEP; 67961d06d6bSBaptiste Daroussin print_endword(h); 68061d06d6bSBaptiste Daroussin } 68161d06d6bSBaptiste Daroussin } 68261d06d6bSBaptiste Daroussin 68361d06d6bSBaptiste Daroussin if ( ! (h->flags & HTML_NONOSPACE)) 68461d06d6bSBaptiste Daroussin h->flags &= ~HTML_NOSPACE; 68561d06d6bSBaptiste Daroussin else 68661d06d6bSBaptiste Daroussin h->flags |= HTML_NOSPACE; 68761d06d6bSBaptiste Daroussin 68861d06d6bSBaptiste Daroussin /* Print out the tag name and attributes. */ 68961d06d6bSBaptiste Daroussin 69061d06d6bSBaptiste Daroussin print_byte(h, '<'); 69161d06d6bSBaptiste Daroussin print_word(h, htmltags[tag].name); 69261d06d6bSBaptiste Daroussin 69361d06d6bSBaptiste Daroussin va_start(ap, fmt); 69461d06d6bSBaptiste Daroussin 6957295610fSBaptiste Daroussin while (*fmt != '\0' && *fmt != 's') { 69661d06d6bSBaptiste Daroussin 69761d06d6bSBaptiste Daroussin /* Parse attributes and arguments. */ 69861d06d6bSBaptiste Daroussin 69961d06d6bSBaptiste Daroussin arg1 = va_arg(ap, char *); 70061d06d6bSBaptiste Daroussin arg2 = NULL; 70161d06d6bSBaptiste Daroussin switch (*fmt++) { 70261d06d6bSBaptiste Daroussin case 'c': 70361d06d6bSBaptiste Daroussin attr = "class"; 70461d06d6bSBaptiste Daroussin break; 70561d06d6bSBaptiste Daroussin case 'h': 70661d06d6bSBaptiste Daroussin attr = "href"; 70761d06d6bSBaptiste Daroussin break; 70861d06d6bSBaptiste Daroussin case 'i': 70961d06d6bSBaptiste Daroussin attr = "id"; 71061d06d6bSBaptiste Daroussin break; 71161d06d6bSBaptiste Daroussin case '?': 71261d06d6bSBaptiste Daroussin attr = arg1; 71361d06d6bSBaptiste Daroussin arg1 = va_arg(ap, char *); 71461d06d6bSBaptiste Daroussin break; 71561d06d6bSBaptiste Daroussin default: 71661d06d6bSBaptiste Daroussin abort(); 71761d06d6bSBaptiste Daroussin } 71861d06d6bSBaptiste Daroussin if (*fmt == 'M') 71961d06d6bSBaptiste Daroussin arg2 = va_arg(ap, char *); 72061d06d6bSBaptiste Daroussin if (arg1 == NULL) 72161d06d6bSBaptiste Daroussin continue; 72261d06d6bSBaptiste Daroussin 72361d06d6bSBaptiste Daroussin /* Print the attributes. */ 72461d06d6bSBaptiste Daroussin 72561d06d6bSBaptiste Daroussin print_byte(h, ' '); 72661d06d6bSBaptiste Daroussin print_word(h, attr); 72761d06d6bSBaptiste Daroussin print_byte(h, '='); 72861d06d6bSBaptiste Daroussin print_byte(h, '"'); 72961d06d6bSBaptiste Daroussin switch (*fmt) { 73061d06d6bSBaptiste Daroussin case 'I': 73161d06d6bSBaptiste Daroussin print_href(h, arg1, NULL, 0); 73261d06d6bSBaptiste Daroussin fmt++; 73361d06d6bSBaptiste Daroussin break; 73461d06d6bSBaptiste Daroussin case 'M': 73561d06d6bSBaptiste Daroussin print_href(h, arg1, arg2, 1); 73661d06d6bSBaptiste Daroussin fmt++; 73761d06d6bSBaptiste Daroussin break; 73861d06d6bSBaptiste Daroussin case 'R': 73961d06d6bSBaptiste Daroussin print_byte(h, '#'); 74061d06d6bSBaptiste Daroussin print_encode(h, arg1, NULL, 1); 74161d06d6bSBaptiste Daroussin fmt++; 74261d06d6bSBaptiste Daroussin break; 74361d06d6bSBaptiste Daroussin default: 74461d06d6bSBaptiste Daroussin print_encode(h, arg1, NULL, 1); 7457295610fSBaptiste Daroussin break; 7467295610fSBaptiste Daroussin } 7477295610fSBaptiste Daroussin print_byte(h, '"'); 7487295610fSBaptiste Daroussin } 7497295610fSBaptiste Daroussin 7507295610fSBaptiste Daroussin style_written = 0; 7517295610fSBaptiste Daroussin while (*fmt++ == 's') { 7527295610fSBaptiste Daroussin arg1 = va_arg(ap, char *); 7537295610fSBaptiste Daroussin arg2 = va_arg(ap, char *); 7547295610fSBaptiste Daroussin if (arg2 == NULL) 7557295610fSBaptiste Daroussin continue; 7567295610fSBaptiste Daroussin print_byte(h, ' '); 7577295610fSBaptiste Daroussin if (style_written == 0) { 7587295610fSBaptiste Daroussin print_word(h, "style=\""); 7597295610fSBaptiste Daroussin style_written = 1; 7607295610fSBaptiste Daroussin } 76161d06d6bSBaptiste Daroussin print_word(h, arg1); 76261d06d6bSBaptiste Daroussin print_byte(h, ':'); 76361d06d6bSBaptiste Daroussin print_byte(h, ' '); 76461d06d6bSBaptiste Daroussin print_word(h, arg2); 76561d06d6bSBaptiste Daroussin print_byte(h, ';'); 76661d06d6bSBaptiste Daroussin } 7677295610fSBaptiste Daroussin if (style_written) 76861d06d6bSBaptiste Daroussin print_byte(h, '"'); 7697295610fSBaptiste Daroussin 77061d06d6bSBaptiste Daroussin va_end(ap); 77161d06d6bSBaptiste Daroussin 77261d06d6bSBaptiste Daroussin /* Accommodate for "well-formed" singleton escaping. */ 77361d06d6bSBaptiste Daroussin 774*6d38604fSBaptiste Daroussin if (htmltags[tag].flags & HTML_NOSTACK) 77561d06d6bSBaptiste Daroussin print_byte(h, '/'); 77661d06d6bSBaptiste Daroussin 77761d06d6bSBaptiste Daroussin print_byte(h, '>'); 77861d06d6bSBaptiste Daroussin 77961d06d6bSBaptiste Daroussin if (tflags & HTML_NLBEGIN) 78061d06d6bSBaptiste Daroussin print_endline(h); 78161d06d6bSBaptiste Daroussin else 78261d06d6bSBaptiste Daroussin h->flags |= HTML_NOSPACE; 78361d06d6bSBaptiste Daroussin 78461d06d6bSBaptiste Daroussin if (tflags & HTML_INDENT) 78561d06d6bSBaptiste Daroussin h->indent++; 78661d06d6bSBaptiste Daroussin if (tflags & HTML_NOINDENT) 78761d06d6bSBaptiste Daroussin h->noindent++; 78861d06d6bSBaptiste Daroussin 78961d06d6bSBaptiste Daroussin return t; 79061d06d6bSBaptiste Daroussin } 79161d06d6bSBaptiste Daroussin 792*6d38604fSBaptiste Daroussin /* 793*6d38604fSBaptiste Daroussin * Print an element with an optional "id=" attribute. 794*6d38604fSBaptiste Daroussin * If the element has phrasing content and an "id=" attribute, 795*6d38604fSBaptiste Daroussin * also add a permalink: outside if it can be in phrasing context, 796*6d38604fSBaptiste Daroussin * inside otherwise. 797*6d38604fSBaptiste Daroussin */ 798*6d38604fSBaptiste Daroussin struct tag * 799*6d38604fSBaptiste Daroussin print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr, 800*6d38604fSBaptiste Daroussin struct roff_node *n) 801*6d38604fSBaptiste Daroussin { 802*6d38604fSBaptiste Daroussin struct roff_node *nch; 803*6d38604fSBaptiste Daroussin struct tag *ret, *t; 804*6d38604fSBaptiste Daroussin char *id, *href; 805*6d38604fSBaptiste Daroussin 806*6d38604fSBaptiste Daroussin ret = NULL; 807*6d38604fSBaptiste Daroussin id = href = NULL; 808*6d38604fSBaptiste Daroussin if (n->flags & NODE_ID) 809*6d38604fSBaptiste Daroussin id = html_make_id(n, 1); 810*6d38604fSBaptiste Daroussin if (n->flags & NODE_HREF) 811*6d38604fSBaptiste Daroussin href = id == NULL ? html_make_id(n, 2) : id; 812*6d38604fSBaptiste Daroussin if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE) 813*6d38604fSBaptiste Daroussin ret = print_otag(h, TAG_A, "chR", "permalink", href); 814*6d38604fSBaptiste Daroussin t = print_otag(h, elemtype, "ci", cattr, id); 815*6d38604fSBaptiste Daroussin if (ret == NULL) { 816*6d38604fSBaptiste Daroussin ret = t; 817*6d38604fSBaptiste Daroussin if (href != NULL && (nch = n->child) != NULL) { 818*6d38604fSBaptiste Daroussin /* man(7) is safe, it tags phrasing content only. */ 819*6d38604fSBaptiste Daroussin if (n->tok > MDOC_MAX || 820*6d38604fSBaptiste Daroussin htmltags[elemtype].flags & HTML_TOPHRASE) 821*6d38604fSBaptiste Daroussin nch = NULL; 822*6d38604fSBaptiste Daroussin else /* For mdoc(7), beware of nested blocks. */ 823*6d38604fSBaptiste Daroussin while (nch != NULL && nch->type == ROFFT_TEXT) 824*6d38604fSBaptiste Daroussin nch = nch->next; 825*6d38604fSBaptiste Daroussin if (nch == NULL) 826*6d38604fSBaptiste Daroussin print_otag(h, TAG_A, "chR", "permalink", href); 827*6d38604fSBaptiste Daroussin } 828*6d38604fSBaptiste Daroussin } 829*6d38604fSBaptiste Daroussin free(id); 830*6d38604fSBaptiste Daroussin if (id == NULL) 831*6d38604fSBaptiste Daroussin free(href); 832*6d38604fSBaptiste Daroussin return ret; 833*6d38604fSBaptiste Daroussin } 834*6d38604fSBaptiste Daroussin 83561d06d6bSBaptiste Daroussin static void 83661d06d6bSBaptiste Daroussin print_ctag(struct html *h, struct tag *tag) 83761d06d6bSBaptiste Daroussin { 83861d06d6bSBaptiste Daroussin int tflags; 83961d06d6bSBaptiste Daroussin 8407295610fSBaptiste Daroussin if (tag->closed == 0) { 8417295610fSBaptiste Daroussin tag->closed = 1; 84261d06d6bSBaptiste Daroussin if (tag == h->metaf) 84361d06d6bSBaptiste Daroussin h->metaf = NULL; 84461d06d6bSBaptiste Daroussin if (tag == h->tblt) 84561d06d6bSBaptiste Daroussin h->tblt = NULL; 84661d06d6bSBaptiste Daroussin 84761d06d6bSBaptiste Daroussin tflags = htmltags[tag->tag].flags; 84861d06d6bSBaptiste Daroussin if (tflags & HTML_INDENT) 84961d06d6bSBaptiste Daroussin h->indent--; 85061d06d6bSBaptiste Daroussin if (tflags & HTML_NOINDENT) 85161d06d6bSBaptiste Daroussin h->noindent--; 85261d06d6bSBaptiste Daroussin if (tflags & HTML_NLEND) 85361d06d6bSBaptiste Daroussin print_endline(h); 85461d06d6bSBaptiste Daroussin print_indent(h); 85561d06d6bSBaptiste Daroussin print_byte(h, '<'); 85661d06d6bSBaptiste Daroussin print_byte(h, '/'); 85761d06d6bSBaptiste Daroussin print_word(h, htmltags[tag->tag].name); 85861d06d6bSBaptiste Daroussin print_byte(h, '>'); 85961d06d6bSBaptiste Daroussin if (tflags & HTML_NLAFTER) 86061d06d6bSBaptiste Daroussin print_endline(h); 8617295610fSBaptiste Daroussin } 8627295610fSBaptiste Daroussin if (tag->refcnt == 0) { 86361d06d6bSBaptiste Daroussin h->tag = tag->next; 86461d06d6bSBaptiste Daroussin free(tag); 86561d06d6bSBaptiste Daroussin } 8667295610fSBaptiste Daroussin } 86761d06d6bSBaptiste Daroussin 86861d06d6bSBaptiste Daroussin void 86961d06d6bSBaptiste Daroussin print_gen_decls(struct html *h) 87061d06d6bSBaptiste Daroussin { 87161d06d6bSBaptiste Daroussin print_word(h, "<!DOCTYPE html>"); 87261d06d6bSBaptiste Daroussin print_endline(h); 87361d06d6bSBaptiste Daroussin } 87461d06d6bSBaptiste Daroussin 87561d06d6bSBaptiste Daroussin void 87661d06d6bSBaptiste Daroussin print_gen_comment(struct html *h, struct roff_node *n) 87761d06d6bSBaptiste Daroussin { 87861d06d6bSBaptiste Daroussin int wantblank; 87961d06d6bSBaptiste Daroussin 88061d06d6bSBaptiste Daroussin print_word(h, "<!-- This is an automatically generated file." 88161d06d6bSBaptiste Daroussin " Do not edit."); 88261d06d6bSBaptiste Daroussin h->indent = 1; 88361d06d6bSBaptiste Daroussin wantblank = 0; 88461d06d6bSBaptiste Daroussin while (n != NULL && n->type == ROFFT_COMMENT) { 88561d06d6bSBaptiste Daroussin if (strstr(n->string, "-->") == NULL && 88661d06d6bSBaptiste Daroussin (wantblank || *n->string != '\0')) { 88761d06d6bSBaptiste Daroussin print_endline(h); 88861d06d6bSBaptiste Daroussin print_indent(h); 88961d06d6bSBaptiste Daroussin print_word(h, n->string); 89061d06d6bSBaptiste Daroussin wantblank = *n->string != '\0'; 89161d06d6bSBaptiste Daroussin } 89261d06d6bSBaptiste Daroussin n = n->next; 89361d06d6bSBaptiste Daroussin } 89461d06d6bSBaptiste Daroussin if (wantblank) 89561d06d6bSBaptiste Daroussin print_endline(h); 89661d06d6bSBaptiste Daroussin print_word(h, " -->"); 89761d06d6bSBaptiste Daroussin print_endline(h); 89861d06d6bSBaptiste Daroussin h->indent = 0; 89961d06d6bSBaptiste Daroussin } 90061d06d6bSBaptiste Daroussin 90161d06d6bSBaptiste Daroussin void 90261d06d6bSBaptiste Daroussin print_text(struct html *h, const char *word) 90361d06d6bSBaptiste Daroussin { 904*6d38604fSBaptiste Daroussin print_tagged_text(h, word, NULL); 905*6d38604fSBaptiste Daroussin } 906*6d38604fSBaptiste Daroussin 907*6d38604fSBaptiste Daroussin void 908*6d38604fSBaptiste Daroussin print_tagged_text(struct html *h, const char *word, struct roff_node *n) 909*6d38604fSBaptiste Daroussin { 910*6d38604fSBaptiste Daroussin struct tag *t; 911*6d38604fSBaptiste Daroussin char *href; 912*6d38604fSBaptiste Daroussin 913*6d38604fSBaptiste Daroussin /* 914*6d38604fSBaptiste Daroussin * Always wrap text in a paragraph unless already contained in 915*6d38604fSBaptiste Daroussin * some flow container; never put it directly into a section. 916*6d38604fSBaptiste Daroussin */ 917*6d38604fSBaptiste Daroussin 918*6d38604fSBaptiste Daroussin if (h->tag->tag == TAG_SECTION) 919*6d38604fSBaptiste Daroussin print_otag(h, TAG_P, "c", "Pp"); 920*6d38604fSBaptiste Daroussin 921*6d38604fSBaptiste Daroussin /* Output whitespace before this text? */ 922*6d38604fSBaptiste Daroussin 92361d06d6bSBaptiste Daroussin if (h->col && (h->flags & HTML_NOSPACE) == 0) { 92461d06d6bSBaptiste Daroussin if ( ! (HTML_KEEP & h->flags)) { 92561d06d6bSBaptiste Daroussin if (HTML_PREKEEP & h->flags) 92661d06d6bSBaptiste Daroussin h->flags |= HTML_KEEP; 92761d06d6bSBaptiste Daroussin print_endword(h); 92861d06d6bSBaptiste Daroussin } else 92961d06d6bSBaptiste Daroussin print_word(h, " "); 93061d06d6bSBaptiste Daroussin } 93161d06d6bSBaptiste Daroussin 932*6d38604fSBaptiste Daroussin /* 933*6d38604fSBaptiste Daroussin * Optionally switch fonts, optionally write a permalink, then 934*6d38604fSBaptiste Daroussin * print the text, optionally surrounded by HTML whitespace. 935*6d38604fSBaptiste Daroussin */ 936*6d38604fSBaptiste Daroussin 93745a5aec3SBaptiste Daroussin assert(h->metaf == NULL); 93845a5aec3SBaptiste Daroussin print_metaf(h); 93961d06d6bSBaptiste Daroussin print_indent(h); 940*6d38604fSBaptiste Daroussin 941*6d38604fSBaptiste Daroussin if (n != NULL && (href = html_make_id(n, 2)) != NULL) { 942*6d38604fSBaptiste Daroussin t = print_otag(h, TAG_A, "chR", "permalink", href); 943*6d38604fSBaptiste Daroussin free(href); 944*6d38604fSBaptiste Daroussin } else 945*6d38604fSBaptiste Daroussin t = NULL; 946*6d38604fSBaptiste Daroussin 94761d06d6bSBaptiste Daroussin if ( ! print_encode(h, word, NULL, 0)) { 94861d06d6bSBaptiste Daroussin if ( ! (h->flags & HTML_NONOSPACE)) 94961d06d6bSBaptiste Daroussin h->flags &= ~HTML_NOSPACE; 95061d06d6bSBaptiste Daroussin h->flags &= ~HTML_NONEWLINE; 95161d06d6bSBaptiste Daroussin } else 95261d06d6bSBaptiste Daroussin h->flags |= HTML_NOSPACE | HTML_NONEWLINE; 95361d06d6bSBaptiste Daroussin 95445a5aec3SBaptiste Daroussin if (h->metaf != NULL) { 95561d06d6bSBaptiste Daroussin print_tagq(h, h->metaf); 95661d06d6bSBaptiste Daroussin h->metaf = NULL; 957*6d38604fSBaptiste Daroussin } else if (t != NULL) 958*6d38604fSBaptiste Daroussin print_tagq(h, t); 95961d06d6bSBaptiste Daroussin 96061d06d6bSBaptiste Daroussin h->flags &= ~HTML_IGNDELIM; 96161d06d6bSBaptiste Daroussin } 96261d06d6bSBaptiste Daroussin 96361d06d6bSBaptiste Daroussin void 96461d06d6bSBaptiste Daroussin print_tagq(struct html *h, const struct tag *until) 96561d06d6bSBaptiste Daroussin { 9667295610fSBaptiste Daroussin struct tag *this, *next; 96761d06d6bSBaptiste Daroussin 9687295610fSBaptiste Daroussin for (this = h->tag; this != NULL; this = next) { 9697295610fSBaptiste Daroussin next = this == until ? NULL : this->next; 9707295610fSBaptiste Daroussin print_ctag(h, this); 97161d06d6bSBaptiste Daroussin } 97261d06d6bSBaptiste Daroussin } 97361d06d6bSBaptiste Daroussin 9747295610fSBaptiste Daroussin /* 9757295610fSBaptiste Daroussin * Close out all open elements up to but excluding suntil. 9767295610fSBaptiste Daroussin * Note that a paragraph just inside stays open together with it 9777295610fSBaptiste Daroussin * because paragraphs include subsequent phrasing content. 9787295610fSBaptiste Daroussin */ 97961d06d6bSBaptiste Daroussin void 98061d06d6bSBaptiste Daroussin print_stagq(struct html *h, const struct tag *suntil) 98161d06d6bSBaptiste Daroussin { 9827295610fSBaptiste Daroussin struct tag *this, *next; 98361d06d6bSBaptiste Daroussin 9847295610fSBaptiste Daroussin for (this = h->tag; this != NULL; this = next) { 9857295610fSBaptiste Daroussin next = this->next; 9867295610fSBaptiste Daroussin if (this == suntil || (next == suntil && 9877295610fSBaptiste Daroussin (this->tag == TAG_P || this->tag == TAG_PRE))) 9887295610fSBaptiste Daroussin break; 9897295610fSBaptiste Daroussin print_ctag(h, this); 99061d06d6bSBaptiste Daroussin } 99161d06d6bSBaptiste Daroussin } 99261d06d6bSBaptiste Daroussin 99361d06d6bSBaptiste Daroussin 99461d06d6bSBaptiste Daroussin /*********************************************************************** 99561d06d6bSBaptiste Daroussin * Low level output functions. 99661d06d6bSBaptiste Daroussin * They implement line breaking using a short static buffer. 99761d06d6bSBaptiste Daroussin ***********************************************************************/ 99861d06d6bSBaptiste Daroussin 99961d06d6bSBaptiste Daroussin /* 100061d06d6bSBaptiste Daroussin * Buffer one HTML output byte. 100161d06d6bSBaptiste Daroussin * If the buffer is full, flush and deactivate it and start a new line. 100261d06d6bSBaptiste Daroussin * If the buffer is inactive, print directly. 100361d06d6bSBaptiste Daroussin */ 100461d06d6bSBaptiste Daroussin static void 100561d06d6bSBaptiste Daroussin print_byte(struct html *h, char c) 100661d06d6bSBaptiste Daroussin { 100761d06d6bSBaptiste Daroussin if ((h->flags & HTML_BUFFER) == 0) { 100861d06d6bSBaptiste Daroussin putchar(c); 100961d06d6bSBaptiste Daroussin h->col++; 101061d06d6bSBaptiste Daroussin return; 101161d06d6bSBaptiste Daroussin } 101261d06d6bSBaptiste Daroussin 101361d06d6bSBaptiste Daroussin if (h->col + h->bufcol < sizeof(h->buf)) { 101461d06d6bSBaptiste Daroussin h->buf[h->bufcol++] = c; 101561d06d6bSBaptiste Daroussin return; 101661d06d6bSBaptiste Daroussin } 101761d06d6bSBaptiste Daroussin 101861d06d6bSBaptiste Daroussin putchar('\n'); 101961d06d6bSBaptiste Daroussin h->col = 0; 102061d06d6bSBaptiste Daroussin print_indent(h); 102161d06d6bSBaptiste Daroussin putchar(' '); 102261d06d6bSBaptiste Daroussin putchar(' '); 102361d06d6bSBaptiste Daroussin fwrite(h->buf, h->bufcol, 1, stdout); 102461d06d6bSBaptiste Daroussin putchar(c); 102561d06d6bSBaptiste Daroussin h->col = (h->indent + 1) * 2 + h->bufcol + 1; 102661d06d6bSBaptiste Daroussin h->bufcol = 0; 102761d06d6bSBaptiste Daroussin h->flags &= ~HTML_BUFFER; 102861d06d6bSBaptiste Daroussin } 102961d06d6bSBaptiste Daroussin 103061d06d6bSBaptiste Daroussin /* 103161d06d6bSBaptiste Daroussin * If something was printed on the current output line, end it. 103261d06d6bSBaptiste Daroussin * Not to be called right after print_indent(). 103361d06d6bSBaptiste Daroussin */ 103461d06d6bSBaptiste Daroussin void 103561d06d6bSBaptiste Daroussin print_endline(struct html *h) 103661d06d6bSBaptiste Daroussin { 103761d06d6bSBaptiste Daroussin if (h->col == 0) 103861d06d6bSBaptiste Daroussin return; 103961d06d6bSBaptiste Daroussin 104061d06d6bSBaptiste Daroussin if (h->bufcol) { 104161d06d6bSBaptiste Daroussin putchar(' '); 104261d06d6bSBaptiste Daroussin fwrite(h->buf, h->bufcol, 1, stdout); 104361d06d6bSBaptiste Daroussin h->bufcol = 0; 104461d06d6bSBaptiste Daroussin } 104561d06d6bSBaptiste Daroussin putchar('\n'); 104661d06d6bSBaptiste Daroussin h->col = 0; 104761d06d6bSBaptiste Daroussin h->flags |= HTML_NOSPACE; 104861d06d6bSBaptiste Daroussin h->flags &= ~HTML_BUFFER; 104961d06d6bSBaptiste Daroussin } 105061d06d6bSBaptiste Daroussin 105161d06d6bSBaptiste Daroussin /* 105261d06d6bSBaptiste Daroussin * Flush the HTML output buffer. 105361d06d6bSBaptiste Daroussin * If it is inactive, activate it. 105461d06d6bSBaptiste Daroussin */ 105561d06d6bSBaptiste Daroussin static void 105661d06d6bSBaptiste Daroussin print_endword(struct html *h) 105761d06d6bSBaptiste Daroussin { 105861d06d6bSBaptiste Daroussin if (h->noindent) { 105961d06d6bSBaptiste Daroussin print_byte(h, ' '); 106061d06d6bSBaptiste Daroussin return; 106161d06d6bSBaptiste Daroussin } 106261d06d6bSBaptiste Daroussin 106361d06d6bSBaptiste Daroussin if ((h->flags & HTML_BUFFER) == 0) { 106461d06d6bSBaptiste Daroussin h->col++; 106561d06d6bSBaptiste Daroussin h->flags |= HTML_BUFFER; 106661d06d6bSBaptiste Daroussin } else if (h->bufcol) { 106761d06d6bSBaptiste Daroussin putchar(' '); 106861d06d6bSBaptiste Daroussin fwrite(h->buf, h->bufcol, 1, stdout); 106961d06d6bSBaptiste Daroussin h->col += h->bufcol + 1; 107061d06d6bSBaptiste Daroussin } 107161d06d6bSBaptiste Daroussin h->bufcol = 0; 107261d06d6bSBaptiste Daroussin } 107361d06d6bSBaptiste Daroussin 107461d06d6bSBaptiste Daroussin /* 107561d06d6bSBaptiste Daroussin * If at the beginning of a new output line, 107661d06d6bSBaptiste Daroussin * perform indentation and mark the line as containing output. 107761d06d6bSBaptiste Daroussin * Make sure to really produce some output right afterwards, 107861d06d6bSBaptiste Daroussin * but do not use print_otag() for producing it. 107961d06d6bSBaptiste Daroussin */ 108061d06d6bSBaptiste Daroussin static void 108161d06d6bSBaptiste Daroussin print_indent(struct html *h) 108261d06d6bSBaptiste Daroussin { 108361d06d6bSBaptiste Daroussin size_t i; 108461d06d6bSBaptiste Daroussin 1085*6d38604fSBaptiste Daroussin if (h->col || h->noindent) 108661d06d6bSBaptiste Daroussin return; 108761d06d6bSBaptiste Daroussin 108861d06d6bSBaptiste Daroussin h->col = h->indent * 2; 108961d06d6bSBaptiste Daroussin for (i = 0; i < h->col; i++) 109061d06d6bSBaptiste Daroussin putchar(' '); 109161d06d6bSBaptiste Daroussin } 109261d06d6bSBaptiste Daroussin 109361d06d6bSBaptiste Daroussin /* 109461d06d6bSBaptiste Daroussin * Print or buffer some characters 109561d06d6bSBaptiste Daroussin * depending on the current HTML output buffer state. 109661d06d6bSBaptiste Daroussin */ 109761d06d6bSBaptiste Daroussin static void 109861d06d6bSBaptiste Daroussin print_word(struct html *h, const char *cp) 109961d06d6bSBaptiste Daroussin { 110061d06d6bSBaptiste Daroussin while (*cp != '\0') 110161d06d6bSBaptiste Daroussin print_byte(h, *cp++); 110261d06d6bSBaptiste Daroussin } 1103