/* $XTermId: html.c,v 1.25 2026/04/07 22:23:47 tom Exp $ */

/*
 * Copyright 2018-2024,2026	Thomas E. Dickey
 * Copyright 2015,2018		Jens Schweikhardt
 *
 * All Rights Reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the sale,
 * use or other dealings in this Software without prior written
 * authorization.
 */

#include <xterm.h>
#include <version.h>

#define MakeDim(color) \
	color = (unsigned short) ((2 * (unsigned) color) / 3)

#define RGBPCT(c) \
 	((double)c.red   / 655.35), \
	((double)c.green / 655.35), \
	((double)c.blue  / 655.35)

static void dumpHtmlHeader(XtermWidget xw, FILE *fp);
static void dumpHtmlScreen(XtermWidget xw, FILE *fp);
static void dumpHtmlLine(XtermWidget xw, int row, FILE *fp);
static void dumpHtmlFooter(XtermWidget, FILE *fp);
static void writeStyle(XtermWidget, FILE *fp);

void
xtermDumpHtml(XtermWidget xw)
{
    char *saveLocale;
    FILE *fp;

    TRACE(("xtermDumpHtml...\n"));
    saveLocale = xtermSetLocale(LC_NUMERIC, "C");
    fp = create_printfile(xw, ".xhtml");
    if (fp != NULL) {
	dumpHtmlHeader(xw, fp);
	dumpHtmlScreen(xw, fp);
	dumpHtmlFooter(xw, fp);
	fclose(fp);
    }
    xtermResetLocale(LC_NUMERIC, saveLocale);
    TRACE(("...xtermDumpHtml done\n"));
}

static void
dumpHtmlHeader(XtermWidget xw, FILE *fp)
{
    fputs("<?xml version='1.0' encoding='UTF-8'?>\n", fp);
    fputs("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'\n", fp);
    fputs("  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n", fp);
    fputs("<html xmlns='http://www.w3.org/1999/xhtml' lang='en' xml:lang='en'>\n", fp);
    fputs(" <head>\n", fp);
    fprintf(fp, "  <meta name='generator' content='%s'/>\n", xtermVersion());
    fputs("  <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>\n", fp);
    fputs("  <link rel='Stylesheet' type='text/css' href='xterm.css'/>\n", fp);
    fputs("  <title>Xterm</title>\n", fp);
    writeStyle(xw, fp);
    fputs(" </head>\n", fp);
    fputs(" <body>\n", fp);
    fputs("  <div id='vt100'>\n", fp);
    fputs("   <pre>", fp);
    xevents(xw);
}

static void
writeStyle(XtermWidget xw, FILE *fp)
{
    const TScreen *s = TScreenOf(xw);

    fputs("  <style type='text/css'>\n", fp);
    fputs("  body, pre { margin: 0 }\n", fp);
    fputs("  #vt100 {\n", fp);
    fputs("    float: left;\n", fp);
    fprintf(fp, "    font-size: 12pt;\n");
    fprintf(fp, "    border: %upx solid %s;\n", BorderWidth(xw),
	    PixelToCSSColor(xw, BorderPixel(xw)));
    fprintf(fp, "    padding: %dpx;\n", s->border);
    fprintf(fp, "    background: %s\n", PixelToCSSColor(xw, xw->old_background));
    fprintf(fp, "  }\n");
    fputs("  .ul { text-decoration: underline }\n", fp);
    fputs("  .bd { font-weight: bold }\n", fp);
    fputs("  .it { font-style: italic }\n", fp);
    fputs("  .st { text-decoration: line-through }\n", fp);
    fputs("  .lu { text-decoration: line-through underline }\n", fp);
    fputs("  </style>\n", fp);
    xevents(xw);
}

static void
dumpHtmlScreen(XtermWidget xw, FILE *fp)
{
    const TScreen *s = TScreenOf(xw);
    int row;

    for (row = s->top_marg; row <= s->bot_marg; ++row) {
	dumpHtmlLine(xw, row, fp);
    }
}

/*
 * Note: initial and final space around values of class and style
 *       attribute are deliberate. They make it easier for XPath
 *       to test whether a particular name is among the attributes.
 *       It allows expressions such as
 *           [contains(@class, ' ul ')]
 *       instead of the unwieldy
 *           [contains(concat(' ', @class, ' '), ' ul ')]
 *       The ev and od (for even and odd rows) values
 *       avoid empty values when going back to old fg/bg.
 */
static void
dumpHtmlLine(XtermWidget xw, int row, FILE *fp)
{
    TScreen *s = TScreenOf(xw);
    char attrs[2][sizeof
		  "<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%)'>"];
    int attr_index = 0;
    char *attr = &attrs[attr_index][0];
    int inx = ROW2INX(s, row);
    LineData *ld = getLineData(s, inx);
    int col;

    if (ld == NULL)
	return;

    for (col = 0; col < MaxCols(s); col++) {
	XColor fgcolor, bgcolor;
	IChar chr = ld->charData[col];
	int slen = 0;

	fgcolor.pixel = xw->old_foreground;
	bgcolor.pixel = xw->old_background;
#if OPT_ISO_COLORS
	if (ld->attribs[col] & FG_COLOR) {
	    Pixel fg = extract_fg(xw, ld->color[col], ld->attribs[col]);
#if OPT_DIRECT_COLOR
	    if (ld->attribs[col] & ATR_DIRECT_FG)
		fgcolor.pixel = fg;
	    else
#endif
		fgcolor.pixel = s->Acolors[fg].value;
	}
	if (ld->attribs[col] & BG_COLOR) {
	    Pixel bg = extract_bg(xw, ld->color[col], ld->attribs[col]);
#if OPT_DIRECT_COLOR
	    if (ld->attribs[col] & ATR_DIRECT_BG)
		bgcolor.pixel = bg;
	    else
#endif
		bgcolor.pixel = s->Acolors[bg].value;
	}
#endif

	(void) QueryOneColor(xw, &fgcolor);
	(void) QueryOneColor(xw, &bgcolor);
	xevents(xw);

	if (ld->attribs[col] & BLINK) {
	    /* White on red. */
	    fgcolor.red = fgcolor.green = fgcolor.blue = MAX_U_COLOR;
	    bgcolor.red = MAX_U_COLOR;
	    bgcolor.green = bgcolor.blue = 0u;
	}
#if OPT_WIDE_ATTRS
	if (ld->attribs[col] & ATR_FAINT) {
	    MakeDim(fgcolor.red);
	    MakeDim(fgcolor.green);
	    MakeDim(fgcolor.blue);
	}
#endif
	if (ld->attribs[col] & INVERSE) {
	    XColor tmp = fgcolor;
	    fgcolor = bgcolor;
	    bgcolor = tmp;
	}

	slen = sprintf(attr + slen, "<span class=' %s",
		       ((row % 2) ? "ev" : "od"));
	if (ld->attribs[col] & BOLD)
	    slen += sprintf(attr + slen, " bd");
#if OPT_WIDE_ATTRS
	/*
	 * Handle multiple text-decoration properties.
	 * Treat ATR_DBL_UNDER the same as UNDERLINE since there is no
	 * official proper CSS 2.2 way to use double underlining. (E.g.
	 * using border-bottom does not work for successive lines and
	 * "text-decoration: underline double" is a browser extension).
	 */
	if ((ld->attribs[col] & (UNDERLINE | ATR_DBL_UNDER)) &&
	    (ld->attribs[col] & ATR_STRIKEOUT))
	    slen += sprintf(attr + slen, " lu");
	else if (ld->attribs[col] & (UNDERLINE | ATR_DBL_UNDER))
	    slen += sprintf(attr + slen, " ul");
	else if (ld->attribs[col] & ATR_STRIKEOUT)
	    slen += sprintf(attr + slen, " st");

	if (ld->attribs[col] & ATR_ITALIC)
	    slen += sprintf(attr + slen, " it");
#else
	if (ld->attribs[col] & UNDERLINE)
	    slen += sprintf(attr + slen, " ul");
#endif
	slen += sprintf(attr + slen,
			" ' style='color: rgb(%.2f%%, %.2f%%, %.2f%%);",
			RGBPCT(fgcolor));
	(void) sprintf(attr + slen,
		       " background: rgb(%.2f%%, %.2f%%, %.2f%%)'>", RGBPCT(bgcolor));
	if (col == 0) {
	    fputs(attr, fp);
	    attr = &attrs[attr_index ^= 1][0];
	} else {
	    if (strcmp(&attrs[0][0], &attrs[1][0])) {
		fputs("</span>", fp);
		fputs(attr, fp);
		attr = &attrs[attr_index ^= 1][0];
	    }
	}

#if OPT_WIDE_CHARS
	if (chr > 127) {
	    /* Ignore hidden characters. */
	    if (chr != HIDDEN_CHAR) {
		Char temp[10];
		*convertToUTF8(temp, chr) = 0;
		fputs((char *) temp, fp);
	    }
	} else
#endif
	    switch (chr) {
	    case 0:
		fputc(' ', fp);
		break;
	    case '&':
		fputs("&amp;", fp);
		break;
	    case '<':
		fputs("&lt;", fp);
		break;
	    case '>':
		fputs("&gt;", fp);
		break;
	    case ' ':
		fputs("\302\240", fp);
		break;
	    default:
		fputc((int) chr, fp);
	    }
	xevents(xw);
    }
    fprintf(fp, "</span>\n");
    xevents(xw);
}

static void
dumpHtmlFooter(XtermWidget xw, FILE *fp)
{
    fputs("</pre>\n", fp);
    fputs("  </div>\n", fp);
    fputs(" </body>\n", fp);
    fputs("</html>\n", fp);
    xevents(xw);
}

char *
PixelToCSSColor(XtermWidget xw, Pixel p)
{
    static char rgb[sizeof "rgb(100.00%, 100.00%, 100.00%)"];
    XColor c;

    (void) xw;
    c.pixel = p;
    (void) QueryOneColor(xw, &c);
    sprintf(rgb, "rgb(%.2f%%, %.2f%%, %.2f%%)", RGBPCT(c));
    return rgb;
}
