Home | History | Annotate | Line # | Download | only in dist
      1 /* $XTermId: html.c,v 1.24 2024/12/01 20:27:00 tom Exp $ */
      2 
      3 /*
      4  * Copyright 2018-2021,2024	Thomas E. Dickey
      5  * Copyright 2015,2018		Jens Schweikhardt
      6  *
      7  * All Rights Reserved
      8  *
      9  * Permission is hereby granted, free of charge, to any person obtaining a copy
     10  * of this software and associated documentation files (the "Software"), to
     11  * deal in the Software without restriction, including without limitation the
     12  * rights to use, copy, modify, merge, publish, distribute, sublicense,
     13  * and/or sell copies of the Software, and to permit persons to whom the
     14  * Software is furnished to do so, subject to the following conditions:
     15  *
     16  * The above copyright notice and this permission notice shall be included in
     17  * all copies or substantial portions of the Software.
     18  *
     19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     22  * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
     23  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
     24  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     25  * OTHER DEALINGS IN THE SOFTWARE.
     26  *
     27  * Except as contained in this notice, the name(s) of the above copyright
     28  * holders shall not be used in advertising or otherwise to promote the sale,
     29  * use or other dealings in this Software without prior written
     30  * authorization.
     31  */
     32 
     33 #include <xterm.h>
     34 #include <version.h>
     35 
     36 #define MakeDim(color) \
     37 	color = (unsigned short) ((2 * (unsigned) color) / 3)
     38 
     39 #define RGBPCT(c) \
     40  	((double)c.red   / 655.35), \
     41 	((double)c.green / 655.35), \
     42 	((double)c.blue  / 655.35)
     43 
     44 static void dumpHtmlHeader(XtermWidget xw, FILE *fp);
     45 static void dumpHtmlScreen(XtermWidget xw, FILE *fp);
     46 static void dumpHtmlLine(XtermWidget xw, int row, FILE *fp);
     47 static void dumpHtmlFooter(XtermWidget, FILE *fp);
     48 static void writeStyle(XtermWidget, FILE *fp);
     49 
     50 void
     51 xtermDumpHtml(XtermWidget xw)
     52 {
     53     char *saveLocale;
     54     FILE *fp;
     55 
     56     TRACE(("xtermDumpHtml...\n"));
     57     saveLocale = xtermSetLocale(LC_NUMERIC, "C");
     58     fp = create_printfile(xw, ".xhtml");
     59     if (fp != NULL) {
     60 	dumpHtmlHeader(xw, fp);
     61 	dumpHtmlScreen(xw, fp);
     62 	dumpHtmlFooter(xw, fp);
     63 	fclose(fp);
     64     }
     65     xtermResetLocale(LC_NUMERIC, saveLocale);
     66     TRACE(("...xtermDumpHtml done\n"));
     67 }
     68 
     69 static void
     70 dumpHtmlHeader(XtermWidget xw, FILE *fp)
     71 {
     72     fputs("<?xml version='1.0' encoding='UTF-8'?>\n", fp);
     73     fputs("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'\n", fp);
     74     fputs("  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n", fp);
     75     fputs("<html xmlns='http://www.w3.org/1999/xhtml' lang='en' xml:lang='en'>\n", fp);
     76     fputs(" <head>\n", fp);
     77     fprintf(fp, "  <meta name='generator' content='%s'/>\n", xtermVersion());
     78     fputs("  <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>\n", fp);
     79     fputs("  <link rel='Stylesheet' type='text/css' href='xterm.css'/>\n", fp);
     80     fputs("  <title>Xterm</title>\n", fp);
     81     writeStyle(xw, fp);
     82     fputs(" </head>\n", fp);
     83     fputs(" <body>\n", fp);
     84     fputs("  <div id='vt100'>\n", fp);
     85     fputs("   <pre>", fp);
     86     xevents(xw);
     87 }
     88 
     89 static void
     90 writeStyle(XtermWidget xw, FILE *fp)
     91 {
     92     TScreen *s = TScreenOf(xw);
     93 
     94     fputs("  <style type='text/css'>\n", fp);
     95     fputs("  body, pre { margin: 0 }\n", fp);
     96     fputs("  #vt100 {\n", fp);
     97     fputs("    float: left;\n", fp);
     98     fprintf(fp, "    font-size: 12pt;\n");
     99     fprintf(fp, "    border: %upx solid %s;\n", BorderWidth(xw),
    100 	    PixelToCSSColor(xw, BorderPixel(xw)));
    101     fprintf(fp, "    padding: %dpx;\n", s->border);
    102     fprintf(fp, "    background: %s\n", PixelToCSSColor(xw, xw->old_background));
    103     fprintf(fp, "  }\n");
    104     fputs("  .ul { text-decoration: underline }\n", fp);
    105     fputs("  .bd { font-weight: bold }\n", fp);
    106     fputs("  .it { font-style: italic }\n", fp);
    107     fputs("  .st { text-decoration: line-through }\n", fp);
    108     fputs("  .lu { text-decoration: line-through underline }\n", fp);
    109     fputs("  </style>\n", fp);
    110     xevents(xw);
    111 }
    112 
    113 static void
    114 dumpHtmlScreen(XtermWidget xw, FILE *fp)
    115 {
    116     TScreen *s = TScreenOf(xw);
    117     int row;
    118 
    119     for (row = s->top_marg; row <= s->bot_marg; ++row) {
    120 	dumpHtmlLine(xw, row, fp);
    121     }
    122 }
    123 
    124 /*
    125  * Note: initial and final space around values of class and style
    126  *       attribute are deliberate. They make it easier for XPath
    127  *       to test whether a particular name is among the attributes.
    128  *       It allows expressions such as
    129  *           [contains(@class, ' ul ')]
    130  *       instead of the unwieldy
    131  *           [contains(concat(' ', @class, ' '), ' ul ')]
    132  *       The ev and od (for even and odd rows) values
    133  *       avoid empty values when going back to old fg/bg.
    134  */
    135 static void
    136 dumpHtmlLine(XtermWidget xw, int row, FILE *fp)
    137 {
    138     TScreen *s = TScreenOf(xw);
    139     char attrs[2][sizeof
    140 		  "<span class=' ev ul bd it st du ' style='color: rgb(100.00%, 100.00%, 100.00%); background: rgb(100.00%, 100.00%, 100.00%)'>"];
    141     int attr_index = 0;
    142     char *attr = &attrs[attr_index][0];
    143     int inx = ROW2INX(s, row);
    144     LineData *ld = getLineData(s, inx);
    145     int col;
    146 
    147     if (ld == NULL)
    148 	return;
    149 
    150     for (col = 0; col < MaxCols(s); col++) {
    151 	XColor fgcolor, bgcolor;
    152 	IChar chr = ld->charData[col];
    153 	int slen = 0;
    154 
    155 	fgcolor.pixel = xw->old_foreground;
    156 	bgcolor.pixel = xw->old_background;
    157 #if OPT_ISO_COLORS
    158 	if (ld->attribs[col] & FG_COLOR) {
    159 	    Pixel fg = extract_fg(xw, ld->color[col], ld->attribs[col]);
    160 #if OPT_DIRECT_COLOR
    161 	    if (ld->attribs[col] & ATR_DIRECT_FG)
    162 		fgcolor.pixel = fg;
    163 	    else
    164 #endif
    165 		fgcolor.pixel = s->Acolors[fg].value;
    166 	}
    167 	if (ld->attribs[col] & BG_COLOR) {
    168 	    Pixel bg = extract_bg(xw, ld->color[col], ld->attribs[col]);
    169 #if OPT_DIRECT_COLOR
    170 	    if (ld->attribs[col] & ATR_DIRECT_BG)
    171 		bgcolor.pixel = bg;
    172 	    else
    173 #endif
    174 		bgcolor.pixel = s->Acolors[bg].value;
    175 	}
    176 #endif
    177 
    178 	(void) QueryOneColor(xw, &fgcolor);
    179 	(void) QueryOneColor(xw, &bgcolor);
    180 	xevents(xw);
    181 
    182 	if (ld->attribs[col] & BLINK) {
    183 	    /* White on red. */
    184 	    fgcolor.red = fgcolor.green = fgcolor.blue = MAX_U_COLOR;
    185 	    bgcolor.red = MAX_U_COLOR;
    186 	    bgcolor.green = bgcolor.blue = 0u;
    187 	}
    188 #if OPT_WIDE_ATTRS
    189 	if (ld->attribs[col] & ATR_FAINT) {
    190 	    MakeDim(fgcolor.red);
    191 	    MakeDim(fgcolor.green);
    192 	    MakeDim(fgcolor.blue);
    193 	}
    194 #endif
    195 	if (ld->attribs[col] & INVERSE) {
    196 	    XColor tmp = fgcolor;
    197 	    fgcolor = bgcolor;
    198 	    bgcolor = tmp;
    199 	}
    200 
    201 	slen = sprintf(attr + slen, "<span class=' %s",
    202 		       ((row % 2) ? "ev" : "od"));
    203 	if (ld->attribs[col] & BOLD)
    204 	    slen += sprintf(attr + slen, " bd");
    205 #if OPT_WIDE_ATTRS
    206 	/*
    207 	 * Handle multiple text-decoration properties.
    208 	 * Treat ATR_DBL_UNDER the same as UNDERLINE since there is no
    209 	 * official proper CSS 2.2 way to use double underlining. (E.g.
    210 	 * using border-bottom does not work for successive lines and
    211 	 * "text-decoration: underline double" is a browser extension).
    212 	 */
    213 	if ((ld->attribs[col] & (UNDERLINE | ATR_DBL_UNDER)) &&
    214 	    (ld->attribs[col] & ATR_STRIKEOUT))
    215 	    slen += sprintf(attr + slen, " lu");
    216 	else if (ld->attribs[col] & (UNDERLINE | ATR_DBL_UNDER))
    217 	    slen += sprintf(attr + slen, " ul");
    218 	else if (ld->attribs[col] & ATR_STRIKEOUT)
    219 	    slen += sprintf(attr + slen, " st");
    220 
    221 	if (ld->attribs[col] & ATR_ITALIC)
    222 	    slen += sprintf(attr + slen, " it");
    223 #else
    224 	if (ld->attribs[col] & UNDERLINE)
    225 	    slen += sprintf(attr + slen, " ul");
    226 #endif
    227 	slen += sprintf(attr + slen,
    228 			" ' style='color: rgb(%.2f%%, %.2f%%, %.2f%%);",
    229 			RGBPCT(fgcolor));
    230 	(void) sprintf(attr + slen,
    231 		       " background: rgb(%.2f%%, %.2f%%, %.2f%%)'>", RGBPCT(bgcolor));
    232 	if (col == 0) {
    233 	    fputs(attr, fp);
    234 	    attr = &attrs[attr_index ^= 1][0];
    235 	} else {
    236 	    if (strcmp(&attrs[0][0], &attrs[1][0])) {
    237 		fputs("</span>", fp);
    238 		fputs(attr, fp);
    239 		attr = &attrs[attr_index ^= 1][0];
    240 	    }
    241 	}
    242 
    243 #if OPT_WIDE_CHARS
    244 	if (chr > 127) {
    245 	    /* Ignore hidden characters. */
    246 	    if (chr != HIDDEN_CHAR) {
    247 		Char temp[10];
    248 		*convertToUTF8(temp, chr) = 0;
    249 		fputs((char *) temp, fp);
    250 	    }
    251 	} else
    252 #endif
    253 	    switch (chr) {
    254 	    case 0:
    255 		fputc(' ', fp);
    256 		break;
    257 	    case '&':
    258 		fputs("&amp;", fp);
    259 		break;
    260 	    case '<':
    261 		fputs("&lt;", fp);
    262 		break;
    263 	    case '>':
    264 		fputs("&gt;", fp);
    265 		break;
    266 	    case ' ':
    267 		fputs("\302\240", fp);
    268 		break;
    269 	    default:
    270 		fputc((int) chr, fp);
    271 	    }
    272 	xevents(xw);
    273     }
    274     fprintf(fp, "</span>\n");
    275     xevents(xw);
    276 }
    277 
    278 static void
    279 dumpHtmlFooter(XtermWidget xw, FILE *fp)
    280 {
    281     fputs("</pre>\n", fp);
    282     fputs("  </div>\n", fp);
    283     fputs(" </body>\n", fp);
    284     fputs("</html>\n", fp);
    285     xevents(xw);
    286 }
    287 
    288 char *
    289 PixelToCSSColor(XtermWidget xw, Pixel p)
    290 {
    291     static char rgb[sizeof "rgb(100.00%, 100.00%, 100.00%)"];
    292     XColor c;
    293 
    294     (void) xw;
    295     c.pixel = p;
    296     (void) QueryOneColor(xw, &c);
    297     sprintf(rgb, "rgb(%.2f%%, %.2f%%, %.2f%%)", RGBPCT(c));
    298     return rgb;
    299 }
    300