html.c revision ad37e533
1/* $XTermId: html.c,v 1.21 2021/02/25 23:19:16 tom Exp $ */
2
3/*
4 * Copyright 2018-2020,2021	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
44static void dumpHtmlHeader(XtermWidget xw, FILE *fp);
45static void dumpHtmlScreen(XtermWidget xw, FILE *fp);
46static void dumpHtmlLine(XtermWidget xw, int row, FILE *fp);
47static void dumpHtmlFooter(XtermWidget, FILE *fp);
48static void writeStyle(XtermWidget, FILE *fp);
49
50void
51xtermDumpHtml(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 != 0) {
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
69static void
70dumpHtmlHeader(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
89static void
90writeStyle(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
113static void
114dumpHtmlScreen(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 */
135static void
136dumpHtmlLine(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 == 0)
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	XQueryColor(xw->screen.display, xw->core.colormap, &fgcolor);
179	XQueryColor(xw->screen.display, xw->core.colormap, &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
278static void
279dumpHtmlFooter(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
288char *
289PixelToCSSColor(XtermWidget xw, Pixel p)
290{
291    static char rgb[sizeof "rgb(100.00%, 100.00%, 100.00%)"];
292    XColor c;
293
294    c.pixel = p;
295    XQueryColor(xw->screen.display, xw->core.colormap, &c);
296    sprintf(rgb, "rgb(%.2f%%, %.2f%%, %.2f%%)", RGBPCT(c));
297    return rgb;
298}
299