15104ee6eSmrg/* $XTermId: html.c,v 1.24 2024/12/01 20:27:00 tom Exp $ */
22e4f8982Smrg
32e4f8982Smrg/*
45104ee6eSmrg * Copyright 2018-2021,2024	Thomas E. Dickey
5f2e35a3aSmrg * Copyright 2015,2018		Jens Schweikhardt
62e4f8982Smrg *
72e4f8982Smrg * All Rights Reserved
82e4f8982Smrg *
92e4f8982Smrg * Permission is hereby granted, free of charge, to any person obtaining a copy
102e4f8982Smrg * of this software and associated documentation files (the "Software"), to
112e4f8982Smrg * deal in the Software without restriction, including without limitation the
122e4f8982Smrg * rights to use, copy, modify, merge, publish, distribute, sublicense,
132e4f8982Smrg * and/or sell copies of the Software, and to permit persons to whom the
142e4f8982Smrg * Software is furnished to do so, subject to the following conditions:
152e4f8982Smrg *
162e4f8982Smrg * The above copyright notice and this permission notice shall be included in
172e4f8982Smrg * all copies or substantial portions of the Software.
182e4f8982Smrg *
192e4f8982Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
202e4f8982Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
212e4f8982Smrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
222e4f8982Smrg * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
232e4f8982Smrg * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
242e4f8982Smrg * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
252e4f8982Smrg * OTHER DEALINGS IN THE SOFTWARE.
262e4f8982Smrg *
272e4f8982Smrg * Except as contained in this notice, the name(s) of the above copyright
282e4f8982Smrg * holders shall not be used in advertising or otherwise to promote the sale,
292e4f8982Smrg * use or other dealings in this Software without prior written
302e4f8982Smrg * authorization.
312e4f8982Smrg */
322e4f8982Smrg
332e4f8982Smrg#include <xterm.h>
342e4f8982Smrg#include <version.h>
352e4f8982Smrg
36f2e35a3aSmrg#define MakeDim(color) \
37f2e35a3aSmrg	color = (unsigned short) ((2 * (unsigned) color) / 3)
382e4f8982Smrg
39f2e35a3aSmrg#define RGBPCT(c) \
40f2e35a3aSmrg 	((double)c.red   / 655.35), \
41f2e35a3aSmrg	((double)c.green / 655.35), \
42f2e35a3aSmrg	((double)c.blue  / 655.35)
432e4f8982Smrg
442e4f8982Smrgstatic void dumpHtmlHeader(XtermWidget xw, FILE *fp);
452e4f8982Smrgstatic void dumpHtmlScreen(XtermWidget xw, FILE *fp);
462e4f8982Smrgstatic void dumpHtmlLine(XtermWidget xw, int row, FILE *fp);
472e4f8982Smrgstatic void dumpHtmlFooter(XtermWidget, FILE *fp);
482e4f8982Smrgstatic void writeStyle(XtermWidget, FILE *fp);
492e4f8982Smrg
502e4f8982Smrgvoid
512e4f8982SmrgxtermDumpHtml(XtermWidget xw)
522e4f8982Smrg{
53f2e35a3aSmrg    char *saveLocale;
542e4f8982Smrg    FILE *fp;
552e4f8982Smrg
562e4f8982Smrg    TRACE(("xtermDumpHtml...\n"));
57f2e35a3aSmrg    saveLocale = xtermSetLocale(LC_NUMERIC, "C");
58f2e35a3aSmrg    fp = create_printfile(xw, ".xhtml");
595104ee6eSmrg    if (fp != NULL) {
602e4f8982Smrg	dumpHtmlHeader(xw, fp);
612e4f8982Smrg	dumpHtmlScreen(xw, fp);
622e4f8982Smrg	dumpHtmlFooter(xw, fp);
632e4f8982Smrg	fclose(fp);
642e4f8982Smrg    }
65f2e35a3aSmrg    xtermResetLocale(LC_NUMERIC, saveLocale);
662e4f8982Smrg    TRACE(("...xtermDumpHtml done\n"));
672e4f8982Smrg}
682e4f8982Smrg
692e4f8982Smrgstatic void
702e4f8982SmrgdumpHtmlHeader(XtermWidget xw, FILE *fp)
712e4f8982Smrg{
722e4f8982Smrg    fputs("<?xml version='1.0' encoding='UTF-8'?>\n", fp);
732e4f8982Smrg    fputs("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'\n", fp);
74f2e35a3aSmrg    fputs("  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n", fp);
752e4f8982Smrg    fputs("<html xmlns='http://www.w3.org/1999/xhtml' lang='en' xml:lang='en'>\n", fp);
762e4f8982Smrg    fputs(" <head>\n", fp);
772e4f8982Smrg    fprintf(fp, "  <meta name='generator' content='%s'/>\n", xtermVersion());
782e4f8982Smrg    fputs("  <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>\n", fp);
792e4f8982Smrg    fputs("  <link rel='Stylesheet' type='text/css' href='xterm.css'/>\n", fp);
802e4f8982Smrg    fputs("  <title>Xterm</title>\n", fp);
812e4f8982Smrg    writeStyle(xw, fp);
822e4f8982Smrg    fputs(" </head>\n", fp);
832e4f8982Smrg    fputs(" <body>\n", fp);
842e4f8982Smrg    fputs("  <div id='vt100'>\n", fp);
852e4f8982Smrg    fputs("   <pre>", fp);
86f2e35a3aSmrg    xevents(xw);
872e4f8982Smrg}
882e4f8982Smrg
892e4f8982Smrgstatic void
902e4f8982SmrgwriteStyle(XtermWidget xw, FILE *fp)
912e4f8982Smrg{
922e4f8982Smrg    TScreen *s = TScreenOf(xw);
932e4f8982Smrg
942e4f8982Smrg    fputs("  <style type='text/css'>\n", fp);
952e4f8982Smrg    fputs("  body, pre { margin: 0 }\n", fp);
962e4f8982Smrg    fputs("  #vt100 {\n", fp);
972e4f8982Smrg    fputs("    float: left;\n", fp);
982e4f8982Smrg    fprintf(fp, "    font-size: 12pt;\n");
992e4f8982Smrg    fprintf(fp, "    border: %upx solid %s;\n", BorderWidth(xw),
1002e4f8982Smrg	    PixelToCSSColor(xw, BorderPixel(xw)));
1012e4f8982Smrg    fprintf(fp, "    padding: %dpx;\n", s->border);
1022e4f8982Smrg    fprintf(fp, "    background: %s\n", PixelToCSSColor(xw, xw->old_background));
1032e4f8982Smrg    fprintf(fp, "  }\n");
1042e4f8982Smrg    fputs("  .ul { text-decoration: underline }\n", fp);
1052e4f8982Smrg    fputs("  .bd { font-weight: bold }\n", fp);
1062e4f8982Smrg    fputs("  .it { font-style: italic }\n", fp);
1072e4f8982Smrg    fputs("  .st { text-decoration: line-through }\n", fp);
1082e4f8982Smrg    fputs("  .lu { text-decoration: line-through underline }\n", fp);
1092e4f8982Smrg    fputs("  </style>\n", fp);
110f2e35a3aSmrg    xevents(xw);
1112e4f8982Smrg}
1122e4f8982Smrg
1132e4f8982Smrgstatic void
1142e4f8982SmrgdumpHtmlScreen(XtermWidget xw, FILE *fp)
1152e4f8982Smrg{
1162e4f8982Smrg    TScreen *s = TScreenOf(xw);
1172e4f8982Smrg    int row;
1182e4f8982Smrg
1192e4f8982Smrg    for (row = s->top_marg; row <= s->bot_marg; ++row) {
1202e4f8982Smrg	dumpHtmlLine(xw, row, fp);
1212e4f8982Smrg    }
1222e4f8982Smrg}
1232e4f8982Smrg
1242e4f8982Smrg/*
1252e4f8982Smrg * Note: initial and final space around values of class and style
1262e4f8982Smrg *       attribute are deliberate. They make it easier for XPath
1272e4f8982Smrg *       to test whether a particular name is among the attributes.
1282e4f8982Smrg *       It allows expressions such as
1292e4f8982Smrg *           [contains(@class, ' ul ')]
1302e4f8982Smrg *       instead of the unwieldy
1312e4f8982Smrg *           [contains(concat(' ', @class, ' '), ' ul ')]
1322e4f8982Smrg *       The ev and od (for even and odd rows) values
1332e4f8982Smrg *       avoid empty values when going back to old fg/bg.
1342e4f8982Smrg */
1352e4f8982Smrgstatic void
1362e4f8982SmrgdumpHtmlLine(XtermWidget xw, int row, FILE *fp)
1372e4f8982Smrg{
1382e4f8982Smrg    TScreen *s = TScreenOf(xw);
1392e4f8982Smrg    char attrs[2][sizeof
1402e4f8982Smrg		  "<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%)'>"];
1412e4f8982Smrg    int attr_index = 0;
1422e4f8982Smrg    char *attr = &attrs[attr_index][0];
1432e4f8982Smrg    int inx = ROW2INX(s, row);
1442e4f8982Smrg    LineData *ld = getLineData(s, inx);
1452e4f8982Smrg    int col;
1462e4f8982Smrg
1475104ee6eSmrg    if (ld == NULL)
1482e4f8982Smrg	return;
1492e4f8982Smrg
1502e4f8982Smrg    for (col = 0; col < MaxCols(s); col++) {
1512e4f8982Smrg	XColor fgcolor, bgcolor;
1522e4f8982Smrg	IChar chr = ld->charData[col];
1532e4f8982Smrg	int slen = 0;
1542e4f8982Smrg
1552e4f8982Smrg	fgcolor.pixel = xw->old_foreground;
1562e4f8982Smrg	bgcolor.pixel = xw->old_background;
1572e4f8982Smrg#if OPT_ISO_COLORS
1582e4f8982Smrg	if (ld->attribs[col] & FG_COLOR) {
159f2e35a3aSmrg	    Pixel fg = extract_fg(xw, ld->color[col], ld->attribs[col]);
160f2e35a3aSmrg#if OPT_DIRECT_COLOR
161f2e35a3aSmrg	    if (ld->attribs[col] & ATR_DIRECT_FG)
162f2e35a3aSmrg		fgcolor.pixel = fg;
163f2e35a3aSmrg	    else
164f2e35a3aSmrg#endif
165f2e35a3aSmrg		fgcolor.pixel = s->Acolors[fg].value;
1662e4f8982Smrg	}
1672e4f8982Smrg	if (ld->attribs[col] & BG_COLOR) {
168f2e35a3aSmrg	    Pixel bg = extract_bg(xw, ld->color[col], ld->attribs[col]);
169f2e35a3aSmrg#if OPT_DIRECT_COLOR
170f2e35a3aSmrg	    if (ld->attribs[col] & ATR_DIRECT_BG)
171f2e35a3aSmrg		bgcolor.pixel = bg;
172f2e35a3aSmrg	    else
173f2e35a3aSmrg#endif
174f2e35a3aSmrg		bgcolor.pixel = s->Acolors[bg].value;
1752e4f8982Smrg	}
1762e4f8982Smrg#endif
1772e4f8982Smrg
178a5ae21e4Smrg	(void) QueryOneColor(xw, &fgcolor);
179a5ae21e4Smrg	(void) QueryOneColor(xw, &bgcolor);
180f2e35a3aSmrg	xevents(xw);
181f2e35a3aSmrg
1822e4f8982Smrg	if (ld->attribs[col] & BLINK) {
1832e4f8982Smrg	    /* White on red. */
184ad37e533Smrg	    fgcolor.red = fgcolor.green = fgcolor.blue = MAX_U_COLOR;
185ad37e533Smrg	    bgcolor.red = MAX_U_COLOR;
1862e4f8982Smrg	    bgcolor.green = bgcolor.blue = 0u;
1872e4f8982Smrg	}
1882e4f8982Smrg#if OPT_WIDE_ATTRS
1892e4f8982Smrg	if (ld->attribs[col] & ATR_FAINT) {
190f2e35a3aSmrg	    MakeDim(fgcolor.red);
191f2e35a3aSmrg	    MakeDim(fgcolor.green);
192f2e35a3aSmrg	    MakeDim(fgcolor.blue);
1932e4f8982Smrg	}
1942e4f8982Smrg#endif
1952e4f8982Smrg	if (ld->attribs[col] & INVERSE) {
1962e4f8982Smrg	    XColor tmp = fgcolor;
1972e4f8982Smrg	    fgcolor = bgcolor;
1982e4f8982Smrg	    bgcolor = tmp;
1992e4f8982Smrg	}
2002e4f8982Smrg
2012e4f8982Smrg	slen = sprintf(attr + slen, "<span class=' %s",
2022e4f8982Smrg		       ((row % 2) ? "ev" : "od"));
2032e4f8982Smrg	if (ld->attribs[col] & BOLD)
2042e4f8982Smrg	    slen += sprintf(attr + slen, " bd");
2052e4f8982Smrg#if OPT_WIDE_ATTRS
2062e4f8982Smrg	/*
2072e4f8982Smrg	 * Handle multiple text-decoration properties.
2082e4f8982Smrg	 * Treat ATR_DBL_UNDER the same as UNDERLINE since there is no
2092e4f8982Smrg	 * official proper CSS 2.2 way to use double underlining. (E.g.
2102e4f8982Smrg	 * using border-bottom does not work for successive lines and
2112e4f8982Smrg	 * "text-decoration: underline double" is a browser extension).
2122e4f8982Smrg	 */
2132e4f8982Smrg	if ((ld->attribs[col] & (UNDERLINE | ATR_DBL_UNDER)) &&
2142e4f8982Smrg	    (ld->attribs[col] & ATR_STRIKEOUT))
2152e4f8982Smrg	    slen += sprintf(attr + slen, " lu");
2162e4f8982Smrg	else if (ld->attribs[col] & (UNDERLINE | ATR_DBL_UNDER))
2172e4f8982Smrg	    slen += sprintf(attr + slen, " ul");
2182e4f8982Smrg	else if (ld->attribs[col] & ATR_STRIKEOUT)
2192e4f8982Smrg	    slen += sprintf(attr + slen, " st");
2202e4f8982Smrg
2212e4f8982Smrg	if (ld->attribs[col] & ATR_ITALIC)
2222e4f8982Smrg	    slen += sprintf(attr + slen, " it");
2232e4f8982Smrg#else
2242e4f8982Smrg	if (ld->attribs[col] & UNDERLINE)
2252e4f8982Smrg	    slen += sprintf(attr + slen, " ul");
2262e4f8982Smrg#endif
2272e4f8982Smrg	slen += sprintf(attr + slen,
2282e4f8982Smrg			" ' style='color: rgb(%.2f%%, %.2f%%, %.2f%%);",
2292e4f8982Smrg			RGBPCT(fgcolor));
230913cc679Smrg	(void) sprintf(attr + slen,
231913cc679Smrg		       " background: rgb(%.2f%%, %.2f%%, %.2f%%)'>", RGBPCT(bgcolor));
2322e4f8982Smrg	if (col == 0) {
2332e4f8982Smrg	    fputs(attr, fp);
2342e4f8982Smrg	    attr = &attrs[attr_index ^= 1][0];
2352e4f8982Smrg	} else {
2362e4f8982Smrg	    if (strcmp(&attrs[0][0], &attrs[1][0])) {
2372e4f8982Smrg		fputs("</span>", fp);
2382e4f8982Smrg		fputs(attr, fp);
2392e4f8982Smrg		attr = &attrs[attr_index ^= 1][0];
2402e4f8982Smrg	    }
2412e4f8982Smrg	}
2422e4f8982Smrg
2432e4f8982Smrg#if OPT_WIDE_CHARS
2442e4f8982Smrg	if (chr > 127) {
2452e4f8982Smrg	    /* Ignore hidden characters. */
2462e4f8982Smrg	    if (chr != HIDDEN_CHAR) {
2472e4f8982Smrg		Char temp[10];
2482e4f8982Smrg		*convertToUTF8(temp, chr) = 0;
2492e4f8982Smrg		fputs((char *) temp, fp);
2502e4f8982Smrg	    }
2512e4f8982Smrg	} else
2522e4f8982Smrg#endif
2532e4f8982Smrg	    switch (chr) {
2542e4f8982Smrg	    case 0:
255f2e35a3aSmrg		fputc(' ', fp);
2562e4f8982Smrg		break;
2572e4f8982Smrg	    case '&':
2582e4f8982Smrg		fputs("&amp;", fp);
2592e4f8982Smrg		break;
2602e4f8982Smrg	    case '<':
2612e4f8982Smrg		fputs("&lt;", fp);
2622e4f8982Smrg		break;
2632e4f8982Smrg	    case '>':
2642e4f8982Smrg		fputs("&gt;", fp);
2652e4f8982Smrg		break;
2662e4f8982Smrg	    case ' ':
267f2e35a3aSmrg		fputs("\302\240", fp);
2682e4f8982Smrg		break;
2692e4f8982Smrg	    default:
2702e4f8982Smrg		fputc((int) chr, fp);
2712e4f8982Smrg	    }
272f2e35a3aSmrg	xevents(xw);
2732e4f8982Smrg    }
2742e4f8982Smrg    fprintf(fp, "</span>\n");
275f2e35a3aSmrg    xevents(xw);
2762e4f8982Smrg}
2772e4f8982Smrg
2782e4f8982Smrgstatic void
279f2e35a3aSmrgdumpHtmlFooter(XtermWidget xw, FILE *fp)
2802e4f8982Smrg{
2812e4f8982Smrg    fputs("</pre>\n", fp);
2822e4f8982Smrg    fputs("  </div>\n", fp);
2832e4f8982Smrg    fputs(" </body>\n", fp);
2842e4f8982Smrg    fputs("</html>\n", fp);
285f2e35a3aSmrg    xevents(xw);
2862e4f8982Smrg}
2872e4f8982Smrg
2882e4f8982Smrgchar *
2892e4f8982SmrgPixelToCSSColor(XtermWidget xw, Pixel p)
2902e4f8982Smrg{
2912e4f8982Smrg    static char rgb[sizeof "rgb(100.00%, 100.00%, 100.00%)"];
2922e4f8982Smrg    XColor c;
2932e4f8982Smrg
294a5ae21e4Smrg    (void) xw;
2952e4f8982Smrg    c.pixel = p;
296a5ae21e4Smrg    (void) QueryOneColor(xw, &c);
2972e4f8982Smrg    sprintf(rgb, "rgb(%.2f%%, %.2f%%, %.2f%%)", RGBPCT(c));
2982e4f8982Smrg    return rgb;
2992e4f8982Smrg}
300