missing NULL terminator in set_config_x
[geda-gaf.git] / gaf / export.c
blob8aa406bdabe74ebdb0ff5aa16a8a60bf4b820f0a
1 /*
2 * gEDA/gaf command-line utility
3 * Copyright (C) 2012 Peter Brett <peter@peter-b.co.uk>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <getopt.h>
27 #include <math.h>
28 #include <errno.h>
30 /* Gettext translation */
31 #include "gettext.h"
33 #include <libgeda/libgeda.h>
34 #include <libgeda/libgedaguile.h>
35 #include <libgedacairo/libgedacairo.h>
37 #include <gtk/gtk.h>
38 #include <glib/gstdio.h>
39 #include <cairo.h>
40 #include <cairo-svg.h>
41 #include <cairo-pdf.h>
42 #include <cairo-ps.h>
44 static int export_text_rendered_bounds (void *user_data,
45 OBJECT *object,
46 int *left, int *top,
47 int *right, int *bottom);
48 static void export_layout_page (PAGE *page, cairo_rectangle_t *extents,
49 cairo_matrix_t *mtx);
50 static void export_draw_page (PAGE *page);
52 static void export_png (void);
53 static void export_postscript (gboolean is_eps);
54 static void export_ps (void);
55 static void export_eps (void);
56 static void export_pdf (void);
57 static void export_svg (void);
59 static gdouble export_parse_dist (const gchar *dist);
60 static gboolean export_parse_scale (const gchar *scale);
61 static gboolean export_parse_layout (const gchar *layout);
62 static gboolean export_parse_margins (const gchar *margins);
63 static gboolean export_parse_paper (const gchar *paper);
64 static gboolean export_parse_size (const gchar *size);
65 static void export_config (void);
66 static void export_usage (void);
67 static void export_command_line (int argc, char * const *argv);
69 /* Default pixels-per-inch for raster outputs */
70 #define DEFAULT_DPI 96
71 /* Default margin width in points */
72 #define DEFAULT_MARGIN 18
74 enum ExportFormatFlags {
75 OUTPUT_MULTIPAGE = 1,
76 OUTPUT_POINTS = 2,
77 OUTPUT_PIXELS = 4,
80 struct ExportFormat {
81 gchar *name; /* UTF-8 */
82 gchar *alias; /* UTF-8 */
83 gint flags;
84 void (*func)(void);
87 enum ExportOrientation {
88 ORIENTATION_AUTO,
89 ORIENTATION_LANDSCAPE,
90 ORIENTATION_PORTRAIT,
93 struct ExportSettings {
94 /* Input & output */
95 int infilec;
96 char * const *infilev; /* Filename encoding */
97 const char *outfile; /* Filename encoding */
98 gchar *format; /* UTF-8 */
100 enum ExportOrientation layout;
102 GtkPaperSize *paper;
103 gdouble scale; /* Output scale; defaults to 1 mil per 1 gschem point*/
104 gdouble size[2]; /* Points */
105 gdouble margins[4]; /* Points. Top, right, bottom, left. */
106 gdouble align[2]; /* 0.0 < align < 1.0 for halign and valign */
107 gdouble dpi;
109 gboolean color;
110 gchar *font; /* UTF-8 */
113 static struct ExportFormat formats[] =
115 {"Portable Network Graphics (PNG)", "png", OUTPUT_PIXELS, export_png},
116 {"Postscript (PS)", "ps", OUTPUT_POINTS | OUTPUT_MULTIPAGE, export_ps},
117 {"Encapsulated Postscript (EPS)", "eps", OUTPUT_POINTS, export_eps},
118 {"Portable Document Format (PDF)", "pdf", OUTPUT_POINTS | OUTPUT_MULTIPAGE, export_pdf},
119 {"Scalable Vector Graphics (SVG)", "svg", OUTPUT_POINTS, export_svg},
120 {NULL, NULL, 0, NULL},
123 static EdaRenderer *renderer = NULL;
124 static TOPLEVEL *toplevel = NULL;
126 static struct ExportSettings settings = {
128 NULL,
129 NULL,
130 NULL,
132 ORIENTATION_AUTO,
134 NULL,
135 72.0/1000,
136 {-1, -1},
137 {-1, -1, -1, -1},
138 {0.5,0.5},
139 DEFAULT_DPI,
141 FALSE,
142 NULL,
145 #define bad_arg_msg _("ERROR: Bad argument '%s' to %s option.\n")
146 #define see_help_msg _("\nRun `gaf export --help' for more information.\n")
148 static void
149 cmd_export_impl (void *data, int argc, char **argv)
151 int i;
152 GError *err = NULL;
153 gchar *tmp;
154 const gchar *out_suffix;
155 struct ExportFormat *exporter = NULL;
156 GArray *render_color_map = NULL;
157 gchar *original_cwd = g_get_current_dir ();
159 gtk_init_check (&argc, &argv);
160 scm_init_guile ();
161 libgeda_init ();
162 scm_dynwind_begin (0);
163 toplevel = s_toplevel_new ();
164 edascm_dynwind_toplevel (toplevel);
166 /* Now load rc files, if necessary */
167 if (getenv ("GAF_INHIBIT_RCFILES") == NULL) {
168 g_rc_parse (toplevel, "gaf export", NULL, NULL);
170 i_vars_libgeda_set (toplevel); /* Ugh */
172 /* Parse configuration files */
173 export_config ();
175 /* Parse command-line arguments */
176 export_command_line (argc, argv);
178 /* If no format was specified, try and guess from output
179 * filename. */
180 if (settings.format == NULL) {
181 out_suffix = strrchr (settings.outfile, '.');
182 if (out_suffix != NULL) {
183 out_suffix++; /* Skip '.' */
184 } else {
185 fprintf (stderr,
186 _("ERROR: Cannot infer output format from filename '%s'.\n"),
187 settings.outfile);
188 exit (1);
192 /* Try and find an exporter function */
193 tmp = g_utf8_strdown ((settings.format == NULL) ? out_suffix : settings.format, -1);
194 for (i = 0; formats[i].name != NULL; i++) {
195 if (strcmp (tmp, formats[i].alias) == 0) {
196 exporter = &formats[i];
197 break;
200 if (exporter == NULL) {
201 if (settings.format == NULL) {
202 fprintf (stderr,
203 _("ERROR: Cannot find supported format for filename '%s'.\n"),
204 settings.outfile);
205 exit (1);
206 } else {
207 fprintf (stderr,
208 _("ERROR: Unsupported output format '%s'.\n"),
209 settings.format);
210 fprintf (stderr, see_help_msg);
211 exit (1);
214 g_free (tmp);
216 /* If more than one schematic/symbol file was specified, check that
217 * exporter supports multipage output. */
218 if ((settings.infilec > 1) && !(exporter->flags & OUTPUT_MULTIPAGE)) {
219 fprintf (stderr,
220 _("ERROR: Selected output format does not support multipage output\n"));
221 exit (1);
224 /* Load schematic files */
225 while (optind < argc) {
226 PAGE *page;
227 tmp = argv[optind++];
229 page = s_page_new (toplevel, tmp);
230 if (!f_open (toplevel, page, tmp, &err)) {
231 fprintf (stderr,
232 _("ERROR: Failed to load '%s': %s\n"), tmp,
233 err->message);
234 exit (1);
236 if (g_chdir (original_cwd) != 0) {
237 fprintf (stderr,
238 _("ERROR: Failed to change directory to '%s': %s\n"),
239 original_cwd, g_strerror (errno));
240 exit (1);
244 /* Create renderer */
245 renderer = eda_renderer_new (NULL, NULL);
246 if (settings.font != NULL) {
247 g_object_set (renderer, "font-name", settings.font, NULL);
250 /* Make sure libgeda knows how to calculate the bounds of text
251 * taking into account font etc. */
252 o_text_set_rendered_bounds_func (toplevel,
253 export_text_rendered_bounds,
254 renderer);
256 /* Create color map */
257 render_color_map =
258 g_array_sized_new (FALSE, FALSE, sizeof(COLOR), MAX_COLORS);
259 render_color_map =
260 g_array_append_vals (render_color_map, print_colors, MAX_COLORS);
261 if (!settings.color) {
262 /* Create a black and white color map. All non-background colors
263 * are black. */
264 COLOR white = {~0, ~0, ~0, ~0, TRUE};
265 COLOR black = {0, 0, 0, ~0, TRUE};
266 for (i = 0; i < MAX_COLORS; i++) {
267 COLOR *c = &g_array_index (render_color_map, COLOR, i);
268 if (!c->enabled) continue;
270 if (c->a == 0) {
271 c->enabled = FALSE;
272 continue;
275 if (i == OUTPUT_BACKGROUND_COLOR) {
276 *c = white;
277 } else {
278 *c = black;
282 eda_renderer_set_color_map (renderer, render_color_map);
284 /* Render */
285 exporter->func ();
287 scm_dynwind_end ();
288 exit (0);
291 /* Callback function registered with libgeda to allow the libgeda
292 * "bounds" functions to get text bounds using the renderer. If a
293 * "rendered bounds" function isn't provided, text objects don't get
294 * used when calculating the extents of the drawing. */
295 static int
296 export_text_rendered_bounds (void *user_data, OBJECT *object,
297 int *left, int *top, int *right, int *bottom)
299 int result;
300 double t, l, r, b;
301 EdaRenderer *renderer = EDA_RENDERER (user_data);
302 result = eda_renderer_get_user_bounds (renderer, object, &l, &t, &r, &b);
303 if (result) {
304 *left = lrint (fmin (l,r));
305 *top = lrint (fmin (t, b));
306 *right = lrint (fmax (l, r));
307 *bottom = lrint (fmax (t, b));
309 return result;
312 /* Prints a message and quits with error status if a cairo status
313 * value is not "success". */
314 static inline void
315 export_cairo_check_error (cairo_status_t status)
317 if (status != CAIRO_STATUS_SUCCESS) {
318 fprintf (stderr, _("ERROR: %s.\n"), cairo_status_to_string (status));
319 exit (1);
323 /* Calculates a page layout. If page is NULL, uses the first page
324 * (this is convenient for single-page rendering). The required size
325 * of the page is returned in extents, and the cairo transformation
326 * matrix needed to fit the drawing into the page is returned in mtx.
327 * Takes into account all of the margin/orientation/paper settings,
328 * and the size of the drawing itself. */
329 static void
330 export_layout_page (PAGE *page, cairo_rectangle_t *extents, cairo_matrix_t *mtx)
332 cairo_rectangle_t drawable;
333 int wx_min, wy_min, wx_max, wy_max, w_width, w_height;
334 gboolean landscape = FALSE;
335 gdouble m[4]; /* Calculated margins */
336 gdouble s; /* Calculated scale */
337 gdouble slack[2]; /* Calculated alignment slack */
339 if (page == NULL) {
340 const GList *pages = geda_list_get_glist (toplevel->pages);
341 g_assert (pages != NULL && pages->data != NULL);
342 page = (PAGE *) pages->data;
345 /* Set the margins. If none were provided by the user, get them
346 * from the paper size (if a paper size is being used) or just use a
347 * sensible default. */
348 if (settings.margins[0] >= 0) {
349 memcpy (m, settings.margins, 4*sizeof(gdouble));
350 } else if (settings.paper != NULL) {
351 m[0] = gtk_paper_size_get_default_top_margin (settings.paper, GTK_UNIT_POINTS);
352 m[1] = gtk_paper_size_get_default_left_margin (settings.paper, GTK_UNIT_POINTS);
353 m[2] = gtk_paper_size_get_default_bottom_margin (settings.paper, GTK_UNIT_POINTS);
354 m[3] = gtk_paper_size_get_default_right_margin (settings.paper, GTK_UNIT_POINTS);
355 } else {
356 m[0] = DEFAULT_MARGIN;
357 m[1] = DEFAULT_MARGIN;
358 m[2] = DEFAULT_MARGIN;
359 m[3] = DEFAULT_MARGIN;
362 /* Now calculate extents of objects within page */
363 if (!world_get_object_glist_bounds (toplevel, s_page_objects (page),
364 &wx_min, &wy_min, &wx_max, &wy_max))
365 wx_min = wy_min = wx_max = wy_max = 0;
366 w_width = wx_max - wx_min;
367 w_height = wy_max - wy_min;
369 /* If a size was specified, use it. Otherwise, use paper size, if
370 * provided. Fall back to just using the size of the drawing. */
371 extents->x = extents->y = 0;
372 if (settings.size[0] >= 0) {
373 /* get extents from size */
375 extents->width = settings.size[0];
376 extents->height = settings.size[1];
378 } else if (settings.paper != NULL) {
379 /* get extents from paper */
381 gdouble p_width, p_height;
383 /* Select orientation */
384 switch (settings.layout) {
385 case ORIENTATION_LANDSCAPE:
386 landscape = TRUE;
387 break;
388 case ORIENTATION_PORTRAIT:
389 landscape = FALSE;
390 break;
391 case ORIENTATION_AUTO:
392 default:
393 landscape = (w_width > w_height);
394 break;
397 p_width = gtk_paper_size_get_width (settings.paper, GTK_UNIT_POINTS);
398 p_height = gtk_paper_size_get_height (settings.paper, GTK_UNIT_POINTS);
400 if (landscape) {
401 extents->width = p_height;
402 extents->height = p_width;
403 } else {
404 extents->width = p_width;
405 extents->height = p_height;
407 } else {
408 /* get extents from drawing */
410 extents->width = w_width * settings.scale; /* in points */
411 extents->height = w_height * settings.scale; /* in points */
413 /* If the extents were obtained from the drawing, grow the extents
414 * rather than shrinking the drawable area. This ensures that the
415 * overall aspect ratio of the image remains correct. */
416 extents->width += m[1] + m[3];
417 extents->height += m[0] + m[2];
420 drawable.x = m[1];
421 drawable.y = m[0];
423 drawable.width = extents->width - m[1] - m[3];
424 drawable.height = extents->height - m[0] - m[2];
426 /* Calculate optimum scale */
427 s = fmin (drawable.width / w_width, drawable.height / w_height);
429 /* Calculate alignment slack */
430 slack[0] = fmin (1, fmax (0, settings.align[0])) * (drawable.width - w_width * s);
431 slack[1] = fmin (1, fmax (0, settings.align[1])) * (drawable.height - w_height * s);
433 /* Finally, create and set a cairo transformation matrix that
434 * centres the drawing into the drawable area. */
435 cairo_matrix_init (mtx, s, 0, 0, -s,
436 - wx_min * s + drawable.x + slack[0],
437 (wy_min + w_height) * s + drawable.y + slack[1]);
440 /* Actually draws a page. If page is NULL, uses the first open page. */
441 static void
442 export_draw_page (PAGE *page)
444 const GList *contents;
445 GList *iter;
446 cairo_t *cr;
448 cr = eda_renderer_get_cairo_context (renderer);
450 if (page == NULL) {
451 const GList *pages = geda_list_get_glist (toplevel->pages);
452 g_assert (pages != NULL && pages->data != NULL);
453 page = (PAGE *) pages->data;
456 /* Draw background */
457 eda_cairo_set_source_color (cr, OUTPUT_BACKGROUND_COLOR,
458 eda_renderer_get_color_map (renderer));
459 cairo_paint (cr);
461 /* Draw objects & cues */
462 contents = s_page_objects (page);
463 for (iter = (GList *) contents; iter != NULL; iter = g_list_next (iter))
464 eda_renderer_draw (renderer, (OBJECT *) iter->data);
465 for (iter = (GList *) contents; iter != NULL; iter = g_list_next (iter))
466 eda_renderer_draw_cues (renderer, (OBJECT *) iter->data);
469 static void
470 export_png (void)
472 cairo_surface_t *surface;
473 cairo_t *cr;
474 cairo_matrix_t mtx;
475 cairo_rectangle_t extents;
476 cairo_status_t status;
477 double scale;
479 /* Create a dummy context to permit calculating extents taking text
480 * into account. */
481 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
482 cr = cairo_create (surface);
483 cairo_surface_destroy (surface);
485 g_object_set (renderer,
486 "cairo-context", cr,
487 "render-flags", EDA_RENDERER_FLAG_HINTING,
488 NULL);
490 /* Calculate page layout */
491 export_layout_page (NULL, &extents, &mtx);
492 cairo_destroy (cr);
494 /* Create a rendering surface of the correct size. 'extents' is
495 * measured in points, so we need to use the DPI setting to
496 * transform to pixels. */
497 scale = settings.dpi / 72.0;
498 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
499 (int) ceil (extents.width * scale),
500 (int) ceil (extents.height * scale));
502 /* Create a cairo context and set the transformation matrix. */
503 cr = cairo_create (surface);
504 cairo_scale (cr, scale, scale);
505 cairo_transform (cr, &mtx);
507 /* Set up renderer. We need to enable subpixel hinting. */
508 g_object_set (renderer, "cairo-context", cr, NULL);
510 /* Draw */
511 export_draw_page (NULL);
512 export_cairo_check_error (cairo_surface_status (surface));
514 /* Save to file */
515 status = cairo_surface_write_to_png (surface, settings.outfile);
516 export_cairo_check_error (status);
519 /* Worker function used by both export_ps and export_eps */
520 static void
521 export_postscript (gboolean is_eps)
523 cairo_surface_t *surface;
524 cairo_rectangle_t extents;
525 cairo_matrix_t mtx;
526 cairo_t *cr;
527 GList *iter;
529 /* Create a surface. To begin with, we don't know the size. */
530 surface = cairo_ps_surface_create (settings.outfile, 1, 1);
531 cairo_ps_surface_set_eps (surface, is_eps);
532 cr = cairo_create (surface);
533 g_object_set (renderer, "cairo-context", cr, NULL);
535 for (iter = geda_list_get_glist (toplevel->pages);
536 iter != NULL;
537 iter = g_list_next (iter)) {
538 PAGE *page = (PAGE *) iter->data;
540 export_layout_page (page, &extents, &mtx);
541 cairo_ps_surface_set_size (surface, extents.width, extents.height);
542 cairo_set_matrix (cr, &mtx);
543 export_draw_page (page);
544 cairo_show_page (cr);
547 cairo_surface_finish (surface);
548 export_cairo_check_error (cairo_surface_status (surface));
551 static void
552 export_ps (void)
554 export_postscript (FALSE);
557 static void
558 export_eps (void)
560 export_postscript (TRUE);
563 static void
564 export_pdf (void)
566 cairo_surface_t *surface;
567 cairo_rectangle_t extents;
568 cairo_matrix_t mtx;
569 cairo_t *cr;
570 GList *iter;
572 /* Create a surface. To begin with, we don't know the size. */
573 surface = cairo_pdf_surface_create (settings.outfile, 1, 1);
574 cr = cairo_create (surface);
575 g_object_set (renderer, "cairo-context", cr, NULL);
577 for (iter = geda_list_get_glist (toplevel->pages);
578 iter != NULL;
579 iter = g_list_next (iter)) {
580 PAGE *page = (PAGE *) iter->data;
582 export_layout_page (page, &extents, &mtx);
583 cairo_pdf_surface_set_size (surface, extents.width, extents.height);
584 cairo_set_matrix (cr, &mtx);
585 export_draw_page (page);
586 cairo_show_page (cr);
589 cairo_surface_finish (surface);
590 export_cairo_check_error (cairo_surface_status (surface));
593 static void
594 export_svg ()
596 cairo_surface_t *surface;
597 cairo_rectangle_t extents;
598 cairo_matrix_t mtx;
599 cairo_t *cr;
601 /* Create a surface and run export_layout_page() to figure out
602 * the picture extents and set up the cairo transformation
603 * matrix. The surface is created only in order to force
604 * eda_renderer_default_get_user_bounds() to behave quietly. */
605 surface = cairo_svg_surface_create (settings.outfile, 0, 0);
606 cr = cairo_create (surface);
607 g_object_set (renderer, "cairo-context", cr, NULL);
608 export_layout_page (NULL, &extents, &mtx);
609 cairo_destroy (cr);
611 /* Now create a new surface with the known extents. */
612 surface = cairo_svg_surface_create (settings.outfile,
613 extents.width,
614 extents.height);
615 cr = cairo_create (surface);
616 g_object_set (renderer, "cairo-context", cr, NULL);
618 cairo_set_matrix (cr, &mtx);
619 export_draw_page (NULL);
621 cairo_show_page (cr);
622 cairo_surface_finish (surface);
623 export_cairo_check_error (cairo_surface_status (surface));
626 /* Parse a distance specification. A distance specification consists
627 * of a floating point value followed by an optional two-character
628 * unit name (in, cm, mm, pc, px, or pt, same as CSS). If no unit is
629 * specified, assumes that the unit is pt. This is used for the
630 * --margins, --size and --scale command-line options. */
631 static gdouble
632 export_parse_dist (const gchar *dist)
634 gdouble base, mult;
635 gchar *unit;
636 errno = 0;
637 base = strtod(dist, &unit);
639 if (errno != 0) return -1;
641 if (g_strcmp0 (unit, "in") == 0) {
642 mult = 72.0;
643 } else if (g_strcmp0 (unit, "cm") == 0) {
644 mult = 72.0 / 2.54;
645 } else if (g_strcmp0 (unit, "mm") == 0) {
646 mult = 72.0 / 25.4;
647 } else if (g_strcmp0 (unit, "pc") == 0) { /* Picas */
648 mult = 12.0;
649 } else if (g_strcmp0 (unit, "px") == 0) {
650 mult = 72.0 / settings.dpi;
651 } else if (g_strcmp0 (unit, "pt") == 0
652 || unit[0] == 0) {
653 mult = 1.0;
654 } else {
655 return -1; /* Indicate that parsing unit failed */
658 return mult * base;
661 /* Parse the --align command line option. */
662 static gboolean
663 export_parse_align (const gchar *align)
665 int n;
666 gchar **args;
668 /* Automatic alignment case */
669 if (g_strcmp0 (align, "auto") == 0 || align[0] == 0) {
670 settings.align[0] = settings.align[1] = 0.5;
671 return TRUE;
674 args = g_strsplit_set (align, ":; ", 2);
675 for (n = 0; args[n] != NULL; n++) {
676 gdouble d = strtod (args[n], NULL);
677 if (d < 0 || d > 1) return FALSE;
678 settings.align[n] = d;
680 g_strfreev (args);
682 if (n != 2) return FALSE;
683 return TRUE;
686 /* Parse the --layout command line option and the export.layout config
687 * file setting. */
688 static gboolean
689 export_parse_layout (const gchar *layout)
691 if (g_strcmp0 (layout, "landscape") == 0) {
692 settings.layout = ORIENTATION_LANDSCAPE;
693 } else if (g_strcmp0 (layout, "portrait") == 0) {
694 settings.layout = ORIENTATION_PORTRAIT;
695 } else if (g_strcmp0 (layout, "auto") == 0
696 || layout == NULL
697 || layout[0] == 0) {
698 settings.layout = ORIENTATION_AUTO;
699 } else {
700 return FALSE;
702 return TRUE;
705 /* Parse the --margins command-line option. If the value is "auto" or
706 * empty, sets margins to be determined automatically from paper size
707 * or compiled-in defaults. Otherwise, expects a list of 1-4 distance
708 * specs; see export_parse_dist(). Rules if <4 distances are
709 * specified are as for 'margin' property in CSS. */
710 static gboolean
711 export_parse_margins (const gchar *margins)
713 gint n;
714 gchar **dists;
716 g_assert (margins != NULL);
718 /* Automatic margins case */
719 if (g_strcmp0 (margins, "auto") == 0 || margins[0] == 0) {
720 for (n = 0; n < 4; n++) settings.margins[n] = -1;
721 return TRUE;
724 dists = g_strsplit_set (margins, ":; ", 4);
725 for (n = 0; dists[n] != NULL; n++) {
726 gdouble d = export_parse_dist (dists[n]);
727 if (d < 0) return FALSE;
728 settings.margins[n] = d;
730 g_strfreev (dists);
732 if (n == 1) {
733 /* If only one value is specified, it applies to all four sides. */
734 settings.margins[3] = settings.margins[2]
735 = settings.margins[1] = settings.margins[0];
736 } else if (n == 2) {
737 /* If two values are specified, the first applies to the
738 top/bottom, and the second to left/right. */
739 settings.margins[2] = settings.margins[0];
740 settings.margins[3] = settings.margins[1];
741 } else if (n == 3) {
742 /* If three values are specified, the first applies to the top,
743 the second to left/right, and the third to the bottom. */
744 settings.margins[3] = settings.margins[1];
745 } else if (n != 4) {
746 return FALSE; /* Must correctly specify 1-4 distances + units */
749 return TRUE;
752 /* Parse the --paper option. Clears any size setting. */
753 static gboolean
754 export_parse_paper (const gchar *paper)
756 GtkPaperSize *paper_size = gtk_paper_size_new (paper);
757 if (paper_size == NULL) return FALSE;
759 if (settings.paper != NULL) gtk_paper_size_free (settings.paper);
760 settings.paper = paper_size;
761 /* Must reset size setting to invalid or it will override paper
762 * setting */
763 settings.size[0] = settings.size[1] = -1;
764 return TRUE;
767 /* Parse the --size option, which must either be "auto" (i.e. obtain
768 * size from drawing) or a list of two distances (width/height). */
769 static gboolean
770 export_parse_size (const gchar *size)
772 gint n;
773 gchar **dists;
775 /* Automatic size case */
776 if (g_strcmp0 (size, "auto") == 0 || size[0] == 0) {
777 settings.size[0] = settings.size[1] = -1;
778 return TRUE;
781 dists = g_strsplit_set (size, ":; ", 2);
782 for (n = 0; dists[n] != NULL; n++) {
783 gdouble d = export_parse_dist (dists[n]);
784 if (d < 0) return FALSE;
785 settings.size[n] = d;
787 g_strfreev (dists);
788 if (n != 2) return FALSE;
790 return TRUE;
793 /* Parse the --scale option. The value should be a distance
794 * corresponding to 100 points in gschem (1 default grid spacing). */
795 static gboolean
796 export_parse_scale (const gchar *scale)
798 gdouble d = export_parse_dist (scale);
799 if (d <= 0) return FALSE;
800 settings.scale = d/100;
801 return TRUE;
804 /* Initialise settings from config store. */
805 static void
806 export_config (void)
808 EdaConfig *cfg = eda_config_get_context_for_file (NULL);
809 gchar *str;
810 gdouble *lst;
811 gdouble dval;
812 gdouble bval;
813 gsize n;
814 GError *err = NULL;
816 /* Parse orientation */
817 str = eda_config_get_string (cfg, "export", "layout", NULL);
818 export_parse_layout (str); /* Don't care if it works */
819 g_free (str);
821 /* Parse paper size */
822 str = eda_config_get_string (cfg, "export", "paper", NULL);
823 export_parse_paper (str);
824 g_free (str);
826 /* Parse specific size setting -- always in points */
827 if (eda_config_has_key (cfg, "export", "size", NULL)) {
828 lst = eda_config_get_double_list (cfg, "export", "size", &n, NULL);
829 if (lst != NULL) {
830 if (n >= 2) {
831 memcpy (settings.size, lst, 2*sizeof(gdouble));
833 g_free (lst);
835 /* Since a specific size was provided, ditch the paper size
836 * setting */
837 if (settings.paper != NULL) {
838 gtk_paper_size_free (settings.paper);
839 settings.paper = NULL;
843 /* Parse margins -- always in points */
844 lst = eda_config_get_double_list (cfg, "export", "margins", &n, NULL);
845 if (lst != NULL) {
846 if (n >= 4) { /* In the config file all four sides must be specified */
847 memcpy (settings.margins, lst, 4*sizeof(gdouble));
849 g_free (lst);
852 /* Parse alignment */
853 lst = eda_config_get_double_list (cfg, "export", "align", &n, NULL);
854 if (lst != NULL) {
855 if (n >= 2) { /* Both halign and valign must be specified */
856 memcpy (settings.align, lst, 2*sizeof(gdouble));
858 g_free (lst);
861 /* Parse dpi */
862 dval = eda_config_get_double (cfg, "export", "dpi", &err);
863 if (err == NULL) {
864 settings.dpi = dval;
865 } else {
866 g_clear_error (&err);
869 bval = eda_config_get_boolean (cfg, "export", "monochrome", &err);
870 if (err == NULL) {
871 settings.color = !bval;
872 } else {
873 g_clear_error (&err);
876 str = eda_config_get_string (cfg, "export", "font", NULL);
877 if (str != NULL) {
878 g_free (settings.font);
879 settings.font = str;
883 #define export_short_options "a:cd:f:F:hl:m:o:p:s:k:"
885 static struct option export_long_options[] = {
886 {"no-color", 0, NULL, 2},
887 {"align", 1, NULL, 'a'},
888 {"color", 0, NULL, 'c'},
889 {"dpi", 1, NULL, 'd'},
890 {"format", 1, NULL, 'f'},
891 {"font", 1, NULL, 'F'},
892 {"help", 0, NULL, 'h'},
893 {"layout", 1, NULL, 'l'},
894 {"margins", 1, NULL, 'm'},
895 {"output", 1, NULL, 'o'},
896 {"paper", 1, NULL, 'p'},
897 {"size", 1, NULL, 's'},
898 {"scale", 1, NULL, 'k'},
899 {NULL, 0, NULL, 0},
902 static void
903 export_usage (void)
905 printf (_("Usage: gaf export [OPTION ...] -o OUTPUT [--] FILE ...\n"
906 "\n"
907 "Export gEDA files in various image formats.\n"
908 "\n"
909 " -f, --format=TYPE output format (normally autodetected)\n"
910 " -o, --output=OUTPUT output filename\n"
911 " -p, --paper=NAME select paper size by name\n"
912 " -s, --size=WIDTH;HEIGHT specify exact paper size\n"
913 " -k, --scale=FACTOR specify output scale factor\n"
914 " -l, --layout=ORIENT page orientation\n"
915 " -m, --margins=TOP;LEFT;BOTTOM;RIGHT\n"
916 " set page margins\n"
917 " -a, --align=HALIGN;VALIGN\n"
918 " set alignment of drawing within page\n"
919 " -d, --dpi=DPI pixels-per-inch for raster outputs\n"
920 " -c, --color enable color output\n"
921 " --no-color disable color output\n"
922 " -F, --font=NAME set font family for printing text\n"
923 " -h, --help display usage information and exit\n"
924 "\n"
925 "Please report bugs to %s.\n"),
926 PACKAGE_BUGREPORT);
927 exit (0);
930 /* Helper function for checking that a command-line option value can
931 * be successfully converted to UTF-8. */
932 static inline gchar *
933 export_command_line__utf8_check (gchar *str, gchar *arg)
935 GError *err = NULL;
936 gchar *result;
938 g_assert (str != NULL);
939 g_assert (arg != NULL);
940 result = g_locale_to_utf8 (str, -1, NULL, NULL, &err);
941 if (result == NULL) {
942 fprintf (stderr, bad_arg_msg, optarg, arg);
943 fprintf (stderr, see_help_msg);
944 exit (1);
946 return result;
949 static void
950 export_command_line (int argc, char * const *argv)
952 int c;
953 gchar *str;
955 /* Parse command-line arguments */
956 while ((c = getopt_long (argc, argv, export_short_options,
957 export_long_options, NULL)) != -1) {
958 switch (c) {
959 case 0:
960 /* This is a long-form-only flag option, and has already been
961 * dealt with by getopt_long(). */
962 break;
964 case 2: /* --no-color */
965 settings.color = FALSE;
966 break;
968 case 'a':
969 str = export_command_line__utf8_check (optarg, "-a,--align");
970 if (!export_parse_align (str)) {
971 fprintf (stderr, bad_arg_msg, optarg, "-a,--align");
972 fprintf (stderr, see_help_msg);
973 exit (1);
975 g_free (str);
976 break;
978 case 'c':
979 settings.color = TRUE;
980 break;
982 case 'd':
983 settings.dpi = strtod (optarg, NULL);
984 if (settings.dpi <= 0) {
985 fprintf (stderr, bad_arg_msg, optarg, "-d,--dpi");
986 fprintf (stderr, see_help_msg);
987 exit (1);
989 break;
991 case 'f':
992 g_free (settings.format);
993 settings.format = export_command_line__utf8_check (optarg, "-f,--format");
994 break;
996 case 'F':
997 str = export_command_line__utf8_check (optarg, "-F,--font");
998 g_free (settings.font);
999 settings.font = str;
1000 break;
1002 case 'h':
1003 export_usage ();
1004 break;
1006 case 'k':
1007 str = export_command_line__utf8_check (optarg, "-k,--scale");
1008 if (!export_parse_scale (str)) {
1009 fprintf (stderr, bad_arg_msg, optarg, "-k,--scale");
1010 fprintf (stderr, see_help_msg);
1011 exit (1);
1013 g_free (str);
1014 /* Since a specific scale was provided, ditch the paper size
1015 * setting */
1016 if (settings.paper != NULL) {
1017 gtk_paper_size_free (settings.paper);
1018 settings.paper = NULL;
1020 break;
1022 case 'l':
1023 if (!export_parse_layout (optarg)) {
1024 fprintf (stderr, bad_arg_msg,
1025 optarg, "-l,--layout");
1026 fprintf (stderr, see_help_msg);
1027 exit (1);
1029 break;
1031 case 'm':
1032 str = export_command_line__utf8_check (optarg, "-m,--margins");
1033 if (!export_parse_margins (str)) {
1034 fprintf (stderr, bad_arg_msg, optarg, "-m,--margins");
1035 fprintf (stderr, see_help_msg);
1036 exit (1);
1038 g_free (str);
1039 break;
1041 case 'o':
1042 settings.outfile = optarg;
1043 break;
1045 case 'p':
1046 str = export_command_line__utf8_check (optarg, "-p,--paper");
1047 if (!export_parse_paper (str)) {
1048 fprintf (stderr, bad_arg_msg, optarg, "-p,--paper");
1049 fprintf (stderr, see_help_msg);
1050 exit (1);
1052 g_free (str);
1053 break;
1055 case 's':
1056 str = export_command_line__utf8_check (optarg, "-s,--size");
1057 if (!export_parse_size (str)) {
1058 fprintf (stderr, bad_arg_msg, optarg, "-s,--size");
1059 fprintf (stderr, see_help_msg);
1060 exit (1);
1062 g_free (str);
1063 /* Since a specific size was provided, ditch the paper size
1064 * setting */
1065 if (settings.paper != NULL) {
1066 gtk_paper_size_free (settings.paper);
1067 settings.paper = NULL;
1069 break;
1071 case '?':
1072 /* getopt_long already printed an error message */
1073 fprintf (stderr, see_help_msg);
1074 exit (1);
1075 break;
1076 default:
1077 g_assert_not_reached ();
1081 /* Check that some schematic files to print were provided */
1082 if (argc <= optind) {
1083 fprintf (stderr,
1084 _("ERROR: You must specify at least one input filename.\n"));
1085 fprintf (stderr, see_help_msg);
1086 exit (1);
1088 settings.infilec = argc - optind;
1089 settings.infilev = &argv[optind];
1091 if (settings.outfile == NULL) {
1092 fprintf (stderr,
1093 _("ERROR: You must specify an output filename.\n"));
1094 fprintf (stderr, see_help_msg);
1095 exit (1);
1099 /* Main function for `gaf export' */
1101 cmd_export (int argc, char **argv)
1103 scm_boot_guile (argc, argv, cmd_export_impl, NULL); /* Doesn't return */
1104 return 0;