print_stream: add a new print_line_color() method
[metze/wireshark/wip.git] / epan / print_stream.c
1 /* print_stream.c
2  * Routines for print streams.
3  *
4  * Gilbert Ramirez <gram@alumni.rice.edu>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24
25 #include "config.h"
26
27 #include <stdio.h>
28 #include <string.h>
29
30 #ifdef _WIN32
31 #include <windows.h>
32 #endif
33
34 #include <glib.h>
35
36 #include <epan/print_stream.h>
37
38 #include <epan/ps.h>
39
40 #include <wsutil/file_util.h>
41
42 #define TERM_SGR_RESET "\x1B[0m"  /* SGR - reset */
43 #define TERM_CSI_EL    "\x1B[K"   /* EL - Erase in Line (to end of line) */
44
45 #ifdef _WIN32
46 static void
47 print_color_escape(FILE *fh, const color_t *fg, const color_t *bg)
48 {
49     /* default to white foreground, black background */
50     WORD win_fg_color = FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN;
51     WORD win_bg_color = 0;
52
53     /* Windows seems to offer 1-bit color, so you can't set the red, green, or blue intensities,
54      * you can only set "{foreground, background} contains {red, green, blue}".
55      * So include red, green or blue if the numeric intensity is high enough
56      */
57     if (fg) {
58         if (((fg->red >> 8) & 0xff) >= 0x80)
59         {
60             win_fg_color |= FOREGROUND_RED;
61         }
62         else
63         {
64             win_fg_color &= (~FOREGROUND_RED);
65         }
66         if (((fg->green >> 8) & 0xff) >= 0x80)
67         {
68             win_fg_color |= FOREGROUND_GREEN;
69         }
70         else
71         {
72             win_fg_color &= (~FOREGROUND_GREEN);
73         }
74         if (((fg->blue >> 8) & 0xff) >= 0x80)
75         {
76             win_fg_color |= FOREGROUND_BLUE;
77         }
78         else
79         {
80             win_fg_color &= (~FOREGROUND_BLUE);
81         }
82     }
83
84     if (bg) {
85         if (((bg->red >> 8) & 0xff) >= 0x80)
86         {
87             win_bg_color |= BACKGROUND_RED;
88         }
89         else
90         {
91             win_bg_color &= (~BACKGROUND_RED);
92         }
93         if (((bg->green >> 8) & 0xff) >= 0x80)
94         {
95             win_bg_color |= BACKGROUND_GREEN;
96         }
97         else
98         {
99             win_bg_color &= (~BACKGROUND_GREEN);
100         }
101         if (((bg->blue >> 8) & 0xff) >= 0x80)
102         {
103             win_bg_color |= BACKGROUND_BLUE;
104         }
105         else
106         {
107             win_bg_color &= (~BACKGROUND_BLUE);
108         }
109     }
110
111     SetConsoleTextAttribute((HANDLE)_get_osfhandle(_fileno(fh)), win_fg_color|win_bg_color);
112 }
113 #else
114 static void
115 print_color_escape(FILE *fh, const color_t *fg, const color_t *bg)
116 {
117     if (fg) {
118         /*
119          * emit 24-bit "true color" escape sequence if output is going to a
120          * tty, the sequence should be ignored by terminals that aren't capable
121          */
122         fprintf(fh, "\x1B[38;2;%u;%u;%um",
123                 (fg->red   >> 8) & 0xff,
124                 (fg->green >> 8) & 0xff,
125                 (fg->blue  >> 8) & 0xff);
126     }
127
128     if (bg) {
129         fprintf(fh, "\x1B[48;2;%u;%u;%um",
130                 (bg->red   >> 8) & 0xff,
131                 (bg->green >> 8) & 0xff,
132                 (bg->blue  >> 8) & 0xff);
133     }
134 }
135 #endif
136
137 static void
138 print_color_eol(FILE *fh)
139 {
140     /*
141      * Emit CSI EL to extend current background color all the way to EOL,
142      * otherwise we get a ragged right edge of color wherever the newline
143      * occurs.  It's not perfect in every terminal emulator, but it generally
144      * works.
145      */
146     fprintf(fh, "%s\n%s", TERM_CSI_EL, TERM_SGR_RESET);
147 }
148
149 static FILE *
150 open_print_dest(gboolean to_file, const char *dest)
151 {
152     FILE *fh;
153
154     /* Open the file or command for output */
155     if (to_file)
156         fh = ws_fopen(dest, "w");
157     else
158         fh = popen(dest, "w");
159
160     return fh;
161 }
162
163 static gboolean
164 close_print_dest(gboolean to_file, FILE *fh)
165 {
166     /* Close the file or command */
167     if (to_file)
168         return (fclose(fh) == 0);
169     else
170         return (pclose(fh) == 0);
171 }
172
173 /* Some formats need stuff at the beginning of the output */
174 gboolean
175 print_preamble(print_stream_t *self, gchar *filename, const char *version_string)
176 {
177     return self->ops->print_preamble ? (self->ops->print_preamble)(self, filename, version_string) : TRUE;
178 }
179
180 gboolean
181 print_line(print_stream_t *self, int indent, const char *line)
182 {
183     return (self->ops->print_line)(self, indent, line);
184 }
185
186 gboolean
187 print_line_color(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg)
188 {
189     if (self->ops->print_line_color)
190         return (self->ops->print_line_color)(self, indent, line, fg, bg);
191     else
192         return (self->ops->print_line)(self, indent, line);
193 }
194
195 /* Insert bookmark */
196 gboolean
197 print_bookmark(print_stream_t *self, const gchar *name, const gchar *title)
198 {
199     return self->ops->print_bookmark ? (self->ops->print_bookmark)(self, name, title) : TRUE;
200 }
201
202 gboolean
203 new_page(print_stream_t *self)
204 {
205     return self->ops->new_page ? (self->ops->new_page)(self) : TRUE;
206 }
207
208 /* Some formats need stuff at the end of the output */
209 gboolean
210 print_finale(print_stream_t *self)
211 {
212     return self->ops->print_finale ? (self->ops->print_finale)(self) : TRUE;
213 }
214
215 gboolean
216 destroy_print_stream(print_stream_t *self)
217 {
218     return (self && self->ops && self->ops->destroy) ? (self->ops->destroy)(self) : TRUE;
219 }
220
221 typedef struct {
222     gboolean  to_file;
223     FILE     *fh;
224 } output_text;
225
226 #define MAX_INDENT    160
227
228 /* returns TRUE if the print succeeded, FALSE if there was an error */
229 static gboolean
230 print_line_color_text(print_stream_t *self, int indent, const char *line, const color_t *fg, const color_t *bg)
231 {
232     static char spaces[MAX_INDENT];
233     size_t ret;
234     output_text *output = (output_text *)self->data;
235     unsigned int num_spaces;
236     gboolean emit_color = self->isatty && (fg != NULL || bg != NULL);
237
238     /* should be space, if NUL -> initialize */
239     if (!spaces[0])
240         memset(spaces, ' ', sizeof(spaces));
241
242     if (emit_color) {
243         print_color_escape(output->fh, fg, bg);
244         if (ferror(output->fh))
245             return FALSE;
246     }
247
248     /* Prepare the tabs for printing, depending on tree level */
249     num_spaces = indent * 4;
250     if (num_spaces > MAX_INDENT)
251         num_spaces = MAX_INDENT;
252
253     ret = fwrite(spaces, 1, num_spaces, output->fh);
254     if (ret == num_spaces) {
255         gchar *tty_out = NULL;
256
257         if (self->isatty && self->to_codeset) {
258             /* XXX Allocating a fresh buffer every line probably isn't the
259              * most efficient way to do this. However, this has the side
260              * effect of scrubbing invalid output.
261              */
262             tty_out = g_convert_with_fallback(line, -1, self->to_codeset, "UTF-8", "?", NULL, NULL, NULL);
263         }
264
265         if (tty_out) {
266 #ifdef _WIN32
267             DWORD out_len = (DWORD) wcslen((wchar_t *) tty_out);
268             WriteConsoleW((HANDLE)_get_osfhandle(_fileno(output->fh)), tty_out, out_len, &out_len, NULL);
269 #else
270             fputs(tty_out, output->fh);
271 #endif
272             g_free(tty_out);
273         } else {
274             fputs(line, output->fh);
275         }
276
277         if (emit_color)
278             print_color_eol(output->fh);
279         else
280             putc('\n', output->fh);
281     }
282
283     return !ferror(output->fh);
284 }
285
286 static gboolean
287 print_line_text(print_stream_t *self, int indent, const char *line)
288 {
289     return print_line_color_text(self, indent, line, NULL, NULL);
290 }
291
292 static gboolean
293 new_page_text(print_stream_t *self)
294 {
295     output_text *output = (output_text *)self->data;
296
297     fputs("\f", output->fh);
298     return !ferror(output->fh);
299 }
300
301 static gboolean
302 destroy_text(print_stream_t *self)
303 {
304     output_text *output = (output_text *)self->data;
305     gboolean     ret;
306
307     ret = close_print_dest(output->to_file, output->fh);
308     g_free(output);
309     g_free(self);
310     return ret;
311 }
312
313 static const print_stream_ops_t print_text_ops = {
314     NULL,            /* preamble */
315     print_line_text,
316     NULL,            /* bookmark */
317     new_page_text,
318     NULL,            /* finale */
319     destroy_text,
320     print_line_color_text,
321 };
322
323 static print_stream_t *
324 print_stream_text_alloc(gboolean to_file, FILE *fh)
325 {
326     print_stream_t *stream;
327     output_text    *output;
328 #ifndef _WIN32
329     const gchar *charset;
330     gboolean is_utf8;
331 #endif
332
333     output          = (output_text *)g_malloc(sizeof *output);
334     output->to_file = to_file;
335     output->fh      = fh;
336     stream          = (print_stream_t *)g_malloc0(sizeof (print_stream_t));
337     stream->ops     = &print_text_ops;
338     stream->isatty  = ws_isatty(ws_fileno(fh));
339     stream->data    = output;
340
341 #ifndef _WIN32
342     /* Is there a more reliable way to do this? */
343     is_utf8 = g_get_charset(&charset);
344     if (!is_utf8) {
345         stream->to_codeset = charset;
346     }
347 #else
348     stream->to_codeset = "UTF-16LE";
349 #endif
350
351     return stream;
352 }
353
354 print_stream_t *
355 print_stream_text_new(gboolean to_file, const char *dest)
356 {
357     FILE *fh;
358
359     fh = open_print_dest(to_file, dest);
360     if (fh == NULL)
361         return NULL;
362
363     return print_stream_text_alloc(to_file, fh);
364 }
365
366 print_stream_t *
367 print_stream_text_stdio_new(FILE *fh)
368 {
369     return print_stream_text_alloc(TRUE, fh);
370 }
371
372 typedef struct {
373     gboolean  to_file;
374     FILE     *fh;
375 } output_ps;
376
377 #define MAX_PS_LINE_LENGTH 256
378
379 static
380 void ps_clean_string(char *out, const char *in, int outbuf_size)
381 {
382     int  rd, wr;
383     char c;
384
385     if (in == NULL) {
386         out[0] = '\0';
387         return;
388     }
389
390     for (rd = 0, wr = 0 ; wr < outbuf_size; rd++, wr++ ) {
391         c = in[rd];
392         switch (c) {
393         case '(':
394         case ')':
395         case '\\':
396             out[wr] = '\\';
397             out[++wr] = c;
398             break;
399
400         default:
401             out[wr] = c;
402             break;
403         }
404
405         if (c == 0) {
406             break;
407         }
408     }
409 }
410
411 static gboolean
412 print_preamble_ps(print_stream_t *self, gchar *filename, const char *version_string)
413 {
414     output_ps *output = (output_ps *)self->data;
415     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
416
417     print_ps_preamble(output->fh);
418
419     fputs("%% the page title\n", output->fh);
420     ps_clean_string(psbuffer, filename, MAX_PS_LINE_LENGTH);
421     fprintf(output->fh, "/ws_pagetitle (%s - Wireshark %s) def\n", psbuffer, version_string);
422     fputs("\n", output->fh);
423     return !ferror(output->fh);
424 }
425
426 static gboolean
427 print_line_ps(print_stream_t *self, int indent, const char *line)
428 {
429     output_ps *output = (output_ps *)self->data;
430     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
431
432     ps_clean_string(psbuffer, line, MAX_PS_LINE_LENGTH);
433     fprintf(output->fh, "%d (%s) putline\n", indent, psbuffer);
434     return !ferror(output->fh);
435 }
436
437 static gboolean
438 print_bookmark_ps(print_stream_t *self, const gchar *name, const gchar *title)
439 {
440     output_ps *output = (output_ps *)self->data;
441     char       psbuffer[MAX_PS_LINE_LENGTH]; /* static sized buffer! */
442
443     /*
444      * See the Adobe "pdfmark reference":
445      *
446      *  http://partners.adobe.com/asn/acrobat/docs/pdfmark.pdf
447      *
448      * The pdfmark stuff tells code that turns PostScript into PDF
449      * things that it should do.
450      *
451      * The /OUT stuff creates a bookmark that goes to the
452      * destination with "name" as the name and "title" as the title.
453      *
454      * The "/DEST" creates the destination.
455      */
456     ps_clean_string(psbuffer, title, MAX_PS_LINE_LENGTH);
457     fprintf(output->fh, "[/Dest /%s /Title (%s)   /OUT pdfmark\n", name,
458           psbuffer);
459     fputs("[/View [/XYZ -4 currentpoint matrix currentmatrix matrix defaultmatrix\n",
460           output->fh);
461     fputs("matrix invertmatrix matrix concatmatrix transform exch pop 20 add null]\n",
462           output->fh);
463     fprintf(output->fh, "/Dest /%s /DEST pdfmark\n", name);
464     return !ferror(output->fh);
465 }
466
467 static gboolean
468 new_page_ps(print_stream_t *self)
469 {
470     output_ps *output = (output_ps *)self->data;
471
472     fputs("formfeed\n", output->fh);
473     return !ferror(output->fh);
474 }
475
476 static gboolean
477 print_finale_ps(print_stream_t *self)
478 {
479     output_ps *output = (output_ps *)self->data;
480
481     print_ps_finale(output->fh);
482     return !ferror(output->fh);
483 }
484
485 static gboolean
486 destroy_ps(print_stream_t *self)
487 {
488     output_ps *output = (output_ps *)self->data;
489     gboolean   ret;
490
491     ret = close_print_dest(output->to_file, output->fh);
492     g_free(output);
493     g_free(self);
494     return ret;
495 }
496
497 static const print_stream_ops_t print_ps_ops = {
498     print_preamble_ps,
499     print_line_ps,
500     print_bookmark_ps,
501     new_page_ps,
502     print_finale_ps,
503     destroy_ps,
504     NULL, /* print_line_color */
505 };
506
507 static print_stream_t *
508 print_stream_ps_alloc(gboolean to_file, FILE *fh)
509 {
510     print_stream_t *stream;
511     output_ps      *output;
512
513     output          = (output_ps *)g_malloc(sizeof *output);
514     output->to_file = to_file;
515     output->fh      = fh;
516     stream          = (print_stream_t *)g_malloc(sizeof (print_stream_t));
517     stream->ops     = &print_ps_ops;
518     stream->data    = output;
519
520     return stream;
521 }
522
523 print_stream_t *
524 print_stream_ps_new(gboolean to_file, const char *dest)
525 {
526     FILE *fh;
527
528     fh = open_print_dest(to_file, dest);
529     if (fh == NULL)
530         return NULL;
531
532     return print_stream_ps_alloc(to_file, fh);
533 }
534
535 print_stream_t *
536 print_stream_ps_stdio_new(FILE *fh)
537 {
538     return print_stream_ps_alloc(TRUE, fh);
539 }
540
541 /*
542  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
543  *
544  * Local variables:
545  * c-basic-offset: 4
546  * tab-width: 8
547  * indent-tabs-mode: nil
548  * End:
549  *
550  * vi: set shiftwidth=4 tabstop=8 expandtab:
551  * :indentSize=4:tabSize=8:noTabs=true:
552  */