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