1/* $XTermId: svg.c,v 1.24 2024/12/01 20:27:00 tom Exp $ */
2
3/*
4 * Copyright 2017-2023,2024	Thomas E. Dickey
5 * Copyright 2015-2016,2017	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#define CELLW 10
45#define CELLH 20
46
47static void dumpSvgHeader(XtermWidget xw, FILE *fp);
48static void dumpSvgScreen(XtermWidget xw, FILE *fp);
49static void dumpSvgLine(XtermWidget xw, int row, FILE *fp);
50static void dumpSvgFooter(XtermWidget, FILE *fp);
51
52static int rows = 0;
53static int cols = 0;
54static Dimension bw = 0;	/* borderWidth */
55static int ib = 0;		/* internalBorder */
56
57void
58xtermDumpSvg(XtermWidget xw)
59{
60    char *saveLocale;
61    FILE *fp;
62
63    TRACE(("xtermDumpSvg...\n"));
64    saveLocale = xtermSetLocale(LC_NUMERIC, "C");
65    fp = create_printfile(xw, ".svg");
66    if (fp != NULL) {
67	dumpSvgHeader(xw, fp);
68	dumpSvgScreen(xw, fp);
69	dumpSvgFooter(xw, fp);
70	fclose(fp);
71    }
72    xtermResetLocale(LC_NUMERIC, saveLocale);
73    TRACE(("...xtermDumpSvg done\n"));
74}
75
76static void
77dumpSvgHeader(XtermWidget xw, FILE *fp)
78{
79    TScreen *s = TScreenOf(xw);
80
81    rows = s->bot_marg - s->top_marg + 1;
82    cols = MaxCols(s);
83    bw = BorderWidth(xw);
84    ib = s->border;
85
86    fputs("<?xml version='1.0' encoding='UTF-8'?>\n", fp);
87    fputs("<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN'\n", fp);
88    fputs("  'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>\n", fp);
89    fputs("<svg xmlns='http://www.w3.org/2000/svg'\n", fp);
90    fputs(" version='1.1' baseProfile='full'\n", fp);
91    fprintf(fp, " viewBox='0 0 %d %d'>\n", 2 * (bw + ib) + cols * CELLW, 2 *
92	    (bw + ib) +
93	    rows * CELLH);
94    fprintf(fp, " <desc>%s Screen Dump</desc>\n", xtermVersion());
95    fprintf(fp,
96	    " <g font-size='%.2f' font-family='monospace, monospace'>\n",
97	    0.80 * CELLH);
98    xevents(xw);
99}
100
101static void
102dumpSvgScreen(XtermWidget xw, FILE *fp)
103{
104    TScreen *s = TScreenOf(xw);
105    int row;
106
107    fprintf(fp, "  <rect x='0' y='0' width='%u' height='%u' fill='%s'/>\n",
108	    (unsigned) (cols * CELLW + 2 * (bw + ib)),
109	    (unsigned) (rows * CELLH + 2 * (bw + ib)),
110	    PixelToCSSColor(xw, xw->core.border_pixel));
111    fprintf(fp, "  <rect x='%u' y='%u' width='%u' height='%u' fill='%s'/>\n",
112	    bw, bw,
113	    (unsigned) (MaxCols(s) * CELLW + 2 * ib),
114	    (unsigned) (rows * CELLH + 2 * ib),
115	    PixelToCSSColor(xw, xw->old_background));
116
117    for (row = s->top_marg; row <= s->bot_marg; ++row) {
118	fprintf(fp, "  <!-- Row %d -->\n", row);
119	dumpSvgLine(xw, row, fp);
120    }
121}
122
123static void
124dumpSvgLine(XtermWidget xw, int row, FILE *fp)
125{
126    TScreen *s = TScreenOf(xw);
127    int inx = ROW2INX(s, row);
128    LineData *ld = getLineData(s, inx);
129    int col, sal, i;		/* sal: same attribute length */
130
131    if (ld == NULL)
132	return;
133
134    for (col = 0; col < MaxCols(s); col += sal) {
135	XColor fgcolor, bgcolor;
136
137	/* Count how many consecutive cells have the same color & attributes. */
138	for (sal = 1; col + sal < MaxCols(s); ++sal) {
139#if OPT_ISO_COLORS
140	    if (!isSameCColor(ld->color[col], ld->color[col + sal]))
141		break;
142#endif
143	    if (ld->attribs[col] != ld->attribs[col + sal])
144		break;
145	}
146
147	fgcolor.pixel = xw->old_foreground;
148	bgcolor.pixel = xw->old_background;
149#if OPT_ISO_COLORS
150	if (ld->attribs[col] & FG_COLOR) {
151	    Pixel fg = extract_fg(xw, ld->color[col], ld->attribs[col]);
152#if OPT_DIRECT_COLOR
153	    if (ld->attribs[col] & ATR_DIRECT_FG)
154		fgcolor.pixel = fg;
155	    else
156#endif
157		fgcolor.pixel = s->Acolors[fg].value;
158	}
159	if (ld->attribs[col] & BG_COLOR) {
160	    Pixel bg = extract_bg(xw, ld->color[col], ld->attribs[col]);
161#if OPT_DIRECT_COLOR
162	    if (ld->attribs[col] & ATR_DIRECT_BG)
163		bgcolor.pixel = bg;
164	    else
165#endif
166		bgcolor.pixel = s->Acolors[bg].value;
167	}
168#endif
169
170	(void) QueryOneColor(xw, &fgcolor);
171	(void) QueryOneColor(xw, &bgcolor);
172	xevents(xw);
173
174	if (ld->attribs[col] & BLINK) {
175	    /* White on red. */
176	    fgcolor.red = fgcolor.green = fgcolor.blue = MAX_U_COLOR;
177	    bgcolor.red = MAX_U_COLOR;
178	    bgcolor.green = bgcolor.blue = 0u;
179	}
180#if OPT_WIDE_ATTRS
181	if (ld->attribs[col] & ATR_FAINT) {
182	    MakeDim(fgcolor.red);
183	    MakeDim(fgcolor.green);
184	    MakeDim(fgcolor.blue);
185	}
186#endif
187	if (ld->attribs[col] & INVERSE) {
188	    XColor tmp = fgcolor;
189	    fgcolor = bgcolor;
190	    bgcolor = tmp;
191	}
192
193	/* Draw the background rectangle. */
194	fprintf(fp, "  <rect x='%d' y='%d' ", bw + ib + col * CELLW, bw + ib
195		+ row * CELLH);
196	fprintf(fp, "height='%d' width='%d' ", CELLH, sal * CELLW);
197	fprintf(fp, "fill='rgb(%.2f%%, %.2f%%, %.2f%%)'/>\n", RGBPCT(bgcolor));
198
199	/* Now the <text>. */
200	/*
201	 * SVG: Rendering text strings into a given rectangle is a challenge.
202	 * Some renderers accept and do the right thing with the 'textLength'
203	 * attribute, while others ignore it. The only predictable way to place
204	 * (even monospaced) text properly is to do it character by character.
205	 */
206
207	fprintf(fp, "  <g");
208	if (ld->attribs[col] & BOLD)
209	    fprintf(fp, " font-weight='bold'");
210#if OPT_WIDE_ATTRS
211	if (ld->attribs[col] & ATR_ITALIC)
212	    fprintf(fp, " font-style='italic'");
213#endif
214	fprintf(fp, " fill='rgb(%.2f%%, %.2f%%, %.2f%%)'>\n", RGBPCT(fgcolor));
215
216	for (i = 0; i < sal; ++i) {
217	    IChar chr = ld->charData[col + i];
218
219	    if (chr == ' ')
220		continue;
221	    fprintf(fp, "   <text x='%d' y='%d'>", bw + ib + (col + i) *
222		    CELLW, bw + ib + row * CELLH + (CELLH * 3) / 4);
223#if OPT_WIDE_CHARS
224	    if (chr > 127) {
225		/* Ignore hidden characters. */
226		if (chr != HIDDEN_CHAR) {
227		    Char temp[10];
228		    *convertToUTF8(temp, chr) = 0;
229		    fputs((char *) temp, fp);
230		}
231	    } else
232#endif
233		switch (chr) {
234		case 0:
235		    fputc(' ', fp);
236		    break;
237		case '&':
238		    fputs("&amp;", fp);
239		    break;
240		case '<':
241		    fputs("&lt;", fp);
242		    break;
243		case '>':
244		    fputs("&gt;", fp);
245		    break;
246		default:
247		    fputc((int) chr, fp);
248		}
249	    fprintf(fp, "</text>\n");
250	    xevents(xw);
251	}
252	fprintf(fp, "  </g>\n");
253	xevents(xw);
254
255#define HLINE(x) \
256  fprintf(fp, "  <line x1='%d' y1='%d' " \
257                      "x2='%d' y2='%d' " \
258                  "stroke='rgb(%.2f%%, %.2f%%, %.2f%%)'/>\n", \
259    bw + ib + col * CELLW,         bw + ib + row * CELLH + CELLH - (x), \
260    bw + ib + (col + sal) * CELLW, bw + ib + row * CELLH + CELLH - (x), \
261    RGBPCT(fgcolor))
262
263	/* Now the line attributes. */
264	if (ld->attribs[col] & UNDERLINE) {
265	    HLINE(4);
266	}
267#if OPT_WIDE_ATTRS
268	if (ld->attribs[col] & ATR_STRIKEOUT) {
269	    HLINE(9);
270	}
271	if (ld->attribs[col] & ATR_DBL_UNDER) {
272	    HLINE(3);
273	    HLINE(1);
274	}
275#endif
276	xevents(xw);
277    }
278    xevents(xw);
279}
280
281static void
282dumpSvgFooter(XtermWidget xw, FILE *fp)
283{
284    fputs(" </g>\n</svg>\n", fp);
285    xevents(xw);
286}
287