gaf-config: Use new GFile config sys APIs where appropriate.
[geda-gaf/pcjc2.git] / gaf / export.c
blob84f4ba57c752c41666c693dffd751520a46e1ed5
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
22 #include <version.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <getopt.h>
28 #include <math.h>
29 #include <errno.h>
31 /* Gettext translation */
32 #include "gettext.h"
34 #include <libgeda/libgeda.h>
35 #include <libgeda/libgedaguile.h>
36 #include <libgedacairo/libgedacairo.h>
38 #include <gtk/gtk.h>
39 #include <glib/gstdio.h>
40 #include <cairo.h>
41 #include <cairo-svg.h>
42 #include <cairo-pdf.h>
43 #include <cairo-ps.h>
45 static int export_text_rendered_bounds (void *user_data,
46 OBJECT *object,
47 int *left, int *top,
48 int *right, int *bottom);
49 static void export_layout_page (PAGE *page, cairo_rectangle_t *extents,
50 cairo_matrix_t *mtx);
51 static void export_draw_page (PAGE *page);
53 static void export_png (void);
54 static void export_postscript (gboolean is_eps);
55 static void export_ps (void);
56 static void export_eps (void);
57 static void export_pdf (void);
58 static void export_svg (void);
60 static gdouble export_parse_dist (const gchar *dist);
61 static gboolean export_parse_scale (const gchar *scale);
62 static gboolean export_parse_layout (const gchar *layout);
63 static gboolean export_parse_margins (const gchar *margins);
64 static gboolean export_parse_paper (const gchar *paper);
65 static gboolean export_parse_size (const gchar *size);
66 static void export_config (void);
67 static void export_usage (void);
68 static void export_command_line (int argc, char * const *argv);
70 /* Default pixels-per-inch for raster outputs */
71 #define DEFAULT_DPI 96
72 /* Default margin width in points */
73 #define DEFAULT_MARGIN 18
75 enum ExportFormatFlags {
76 OUTPUT_MULTIPAGE = 1,
77 OUTPUT_POINTS = 2,
78 OUTPUT_PIXELS = 4,
81 struct ExportFormat {
82 gchar *name; /* UTF-8 */
83 gchar *alias; /* UTF-8 */
84 gint flags;
85 void (*func)(void);
88 enum ExportOrientation {
89 ORIENTATION_AUTO,
90 ORIENTATION_LANDSCAPE,
91 ORIENTATION_PORTRAIT,
94 struct ExportSettings {
95 /* Input & output */
96 int infilec;
97 char * const *infilev; /* Filename encoding */
98 const char *outfile; /* Filename encoding */
99 gchar *format; /* UTF-8 */
101 enum ExportOrientation layout;
103 GtkPaperSize *paper;
104 gdouble scale; /* Output scale; defaults to 1 mil per 1 gschem point*/
105 gdouble size[2]; /* Points */
106 gdouble margins[4]; /* Points. Top, right, bottom, left. */
107 gdouble align[2]; /* 0.0 < align < 1.0 for halign and valign */
108 gdouble dpi;
110 gboolean color;
111 gchar *font; /* UTF-8 */
114 static struct ExportFormat formats[] =
116 {"Portable Network Graphics (PNG)", "png", OUTPUT_PIXELS, export_png},
117 {"Postscript (PS)", "ps", OUTPUT_POINTS | OUTPUT_MULTIPAGE, export_ps},
118 {"Encapsulated Postscript (EPS)", "eps", OUTPUT_POINTS, export_eps},
119 {"Portable Document Format (PDF)", "pdf", OUTPUT_POINTS | OUTPUT_MULTIPAGE, export_pdf},
120 {"Scalable Vector Graphics (SVG)", "svg", OUTPUT_POINTS, export_svg},
121 {NULL, NULL, 0, NULL},
124 static EdaRenderer *renderer = NULL;
125 static TOPLEVEL *toplevel = NULL;
127 static struct ExportSettings settings = {
129 NULL,
130 NULL,
131 NULL,
133 ORIENTATION_AUTO,
135 NULL,
136 72.0/1000,
137 {-1, -1},
138 {-1, -1, -1, -1},
139 {0.5,0.5},
140 DEFAULT_DPI,
142 FALSE,
143 NULL,
146 #define bad_arg_msg _("ERROR: Bad argument '%s' to %s option.\n")
147 #define see_help_msg _("\nRun `gaf export --help' for more information.\n")
149 /* Main function for `gaf export' */
150 void
151 cmd_export (int argc, char **argv)
153 int i;
154 GError *err = NULL;
155 gchar *tmp;
156 const gchar *out_suffix;
157 struct ExportFormat *exporter = NULL;
158 GArray *render_color_map = NULL;
159 gchar *original_cwd = g_get_current_dir ();
161 gtk_init_check (&argc, &argv);
162 scm_init_guile ();
163 libgeda_init ();
164 scm_dynwind_begin (0);
165 toplevel = s_toplevel_new ();
166 edascm_dynwind_toplevel (toplevel);
168 /* Now load rc files, if necessary */
169 if (getenv ("GAF_INHIBIT_RCFILES") == NULL) {
170 g_rc_parse (toplevel, "gaf export", NULL, NULL);
172 i_vars_libgeda_set (toplevel); /* Ugh */
174 /* Parse configuration files */
175 export_config ();
177 /* Parse command-line arguments */
178 export_command_line (argc, argv);
180 /* If no format was specified, try and guess from output
181 * filename. */
182 if (settings.format == NULL) {
183 out_suffix = strrchr (settings.outfile, '.');
184 if (out_suffix != NULL) {
185 out_suffix++; /* Skip '.' */
186 } else {
187 fprintf (stderr,
188 _("ERROR: Cannot infer output format from filename '%s'.\n"),
189 settings.outfile);
190 exit (1);
194 /* Try and find an exporter function */
195 tmp = g_utf8_strdown ((settings.format == NULL) ? out_suffix : settings.format, -1);
196 for (i = 0; formats[i].name != NULL; i++) {
197 if (strcmp (tmp, formats[i].alias) == 0) {
198 exporter = &formats[i];
199 break;
202 if (exporter == NULL) {
203 if (settings.format == NULL) {
204 fprintf (stderr,
205 _("ERROR: Cannot find supported format for filename '%s'.\n"),
206 settings.outfile);
207 exit (1);
208 } else {
209 fprintf (stderr,
210 _("ERROR: Unsupported output format '%s'.\n"),
211 settings.format);
212 fprintf (stderr, see_help_msg);
213 exit (1);
216 g_free (tmp);
218 /* If more than one schematic/symbol file was specified, check that
219 * exporter supports multipage output. */
220 if ((settings.infilec > 1) && !(exporter->flags & OUTPUT_MULTIPAGE)) {
221 fprintf (stderr,
222 _("ERROR: Selected output format does not support multipage output\n"));
223 exit (1);
226 /* Load schematic files */
227 while (optind < argc) {
228 PAGE *page;
229 tmp = argv[optind++];
231 page = s_page_new (toplevel, tmp);
232 if (!f_open (toplevel, page, tmp, &err)) {
233 fprintf (stderr,
234 _("ERROR: Failed to load '%s': %s\n"), tmp,
235 err->message);
236 exit (1);
238 if (g_chdir (original_cwd) != 0) {
239 fprintf (stderr,
240 _("ERROR: Failed to change directory to '%s': %s\n"),
241 original_cwd, g_strerror (errno));
242 exit (1);
246 /* Create renderer */
247 renderer = eda_renderer_new (NULL, NULL);
248 if (settings.font != NULL) {
249 g_object_set (renderer, "font-name", settings.font, NULL);
252 /* Make sure libgeda knows how to calculate the bounds of text
253 * taking into account font etc. */
254 o_text_set_rendered_bounds_func (toplevel,
255 export_text_rendered_bounds,
256 renderer);
258 /* Create color map */
259 render_color_map =
260 g_array_sized_new (FALSE, FALSE, sizeof(COLOR), MAX_COLORS);
261 render_color_map =
262 g_array_append_vals (render_color_map, print_colors, MAX_COLORS);
263 if (!settings.color) {
264 /* Create a black and white color map. All non-background colors
265 * are black. */
266 COLOR white = {~0, ~0, ~0, ~0, TRUE};
267 COLOR black = {0, 0, 0, ~0, TRUE};
268 for (i = 0; i < MAX_COLORS; i++) {
269 COLOR *c = &g_array_index (render_color_map, COLOR, i);
270 if (!c->enabled) continue;
272 if (c->a == 0) {
273 c->enabled = FALSE;
274 continue;
277 if (i == OUTPUT_BACKGROUND_COLOR) {
278 *c = white;
279 } else {
280 *c = black;
284 eda_renderer_set_color_map (renderer, render_color_map);
286 /* Render */
287 exporter->func ();
289 scm_dynwind_end ();
290 exit (0);
293 /* Callback function registered with libgeda to allow the libgeda
294 * "bounds" functions to get text bounds using the renderer. If a
295 * "rendered bounds" function isn't provided, text objects don't get
296 * used when calculating the extents of the drawing. */
297 static int
298 export_text_rendered_bounds (void *user_data, OBJECT *object,
299 int *left, int *top, int *right, int *bottom)
301 int result;
302 double t, l, r, b;
303 EdaRenderer *renderer = EDA_RENDERER (user_data);
304 result = eda_renderer_get_user_bounds (renderer, object, &l, &t, &r, &b);
305 *left = lrint (fmin (l,r));
306 *top = lrint (fmin (t, b));
307 *right = lrint (fmax (l, r));
308 *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_matrix_t tmp_mtx;
333 cairo_rectangle_t drawable;
334 cairo_t *cr;
335 int wx_min, wy_min, wx_max, wy_max, w_width, w_height;
336 gboolean landscape = FALSE;
337 gboolean size_from_paper = FALSE;
338 gboolean size_from_drawing = FALSE;
339 gdouble m[4]; /* Calculated margins */
340 gdouble s; /* Calculated scale */
341 gdouble slack[2]; /* Calculated alignment slack */
343 cr = eda_renderer_get_cairo_context (renderer);
345 if (page == NULL) {
346 const GList *pages = geda_list_get_glist (toplevel->pages);
347 g_assert (pages != NULL && pages->data != NULL);
348 page = (PAGE *) pages->data;
351 /* Calculate extents of objects within page */
352 cairo_matrix_init (&tmp_mtx, 1, 0, 0, -1, -1, -1); /* Very vague approximation */
353 cairo_set_matrix (cr, &tmp_mtx);
354 world_get_object_glist_bounds (toplevel, s_page_objects (page),
355 &wx_min, &wy_min, &wx_max, &wy_max);
356 w_width = wx_max - wx_min;
357 w_height = wy_max - wy_min;
359 /* If a size was specified, use it. Otherwise, use paper size, if
360 * provided. Fall back to just using the size of the drawing. */
361 extents->x = extents->y = 0;
362 if (settings.size[0] >= 0) {
364 extents->width = settings.size[0];
365 extents->height = settings.size[1];
367 } else if (settings.paper != NULL) {
368 gdouble p_width, p_height;
370 /* Select orientation */
371 switch (settings.layout) {
372 case ORIENTATION_LANDSCAPE:
373 landscape = TRUE;
374 break;
375 case ORIENTATION_PORTRAIT:
376 landscape = FALSE;
377 break;
378 case ORIENTATION_AUTO:
379 default:
380 landscape = (w_width > w_height);
381 break;
384 p_width = gtk_paper_size_get_width (settings.paper, GTK_UNIT_POINTS);
385 p_height = gtk_paper_size_get_height (settings.paper, GTK_UNIT_POINTS);
387 if (landscape) {
388 extents->width = p_height;
389 extents->height = p_width;
390 } else {
391 extents->width = p_width;
392 extents->height = p_height;
395 size_from_paper = TRUE;
397 } else {
399 extents->width = w_width * settings.scale; /* in points */
400 extents->height = w_height * settings.scale; /* in points */
402 size_from_drawing = TRUE;
405 /* Now set the margins. If none were provided by the user, get them
406 * from the paper size (if a paper size is being used) or just use a
407 * sensible default. */
408 if (settings.margins[0] >= 0) {
409 memcpy (m, settings.margins, 4*sizeof(gdouble));
410 } else if (size_from_paper) {
411 m[0] = gtk_paper_size_get_default_top_margin (settings.paper, GTK_UNIT_POINTS);
412 m[1] = gtk_paper_size_get_default_left_margin (settings.paper, GTK_UNIT_POINTS);
413 m[2] = gtk_paper_size_get_default_bottom_margin (settings.paper, GTK_UNIT_POINTS);
414 m[3] = gtk_paper_size_get_default_right_margin (settings.paper, GTK_UNIT_POINTS);
415 } else {
416 m[0] = DEFAULT_MARGIN;
417 m[1] = DEFAULT_MARGIN;
418 m[2] = DEFAULT_MARGIN;
419 m[3] = DEFAULT_MARGIN;
422 drawable.x = m[1];
423 drawable.y = m[0];
425 /* If the extents were obtained from the drawing, grow the extents
426 * rather than shrinking the drawable area. This ensures that the
427 * overall aspect ratio of the image remains correct. */
428 if (size_from_drawing) {
429 extents->width += m[1] + m[3];
430 extents->height += m[0] + m[2];
433 drawable.width = extents->width - m[1] - m[3];
434 drawable.height = extents->height - m[0] - m[2];
436 /* Calculate optimum scale */
437 s = fmin (drawable.width / w_width, drawable.height / w_height);
439 /* Calculate alignment slack */
440 slack[0] = fmin (1, fmax (0, settings.align[0])) * (drawable.width - w_width * s);
441 slack[1] = fmin (1, fmax (0, settings.align[1])) * (drawable.height - w_height * s);
443 /* Finally, create and set a cairo transformation matrix that
444 * centres the drawing into the drawable area. */
445 cairo_matrix_init (mtx, s, 0, 0, -s,
446 - wx_min * s + drawable.x + slack[0],
447 (wy_min + w_height) * s + drawable.y + slack[1]);
450 /* Actually draws a page. If page is NULL, uses the first open page. */
451 static void
452 export_draw_page (PAGE *page)
454 const GList *contents;
455 GList *iter;
456 cairo_t *cr;
458 cr = eda_renderer_get_cairo_context (renderer);
460 if (page == NULL) {
461 const GList *pages = geda_list_get_glist (toplevel->pages);
462 g_assert (pages != NULL && pages->data != NULL);
463 page = (PAGE *) pages->data;
466 /* Draw background */
467 eda_cairo_set_source_color (cr, OUTPUT_BACKGROUND_COLOR,
468 eda_renderer_get_color_map (renderer));
469 cairo_paint (cr);
471 /* Draw objects & cues */
472 contents = s_page_objects (page);
473 for (iter = (GList *) contents; iter != NULL; iter = g_list_next (iter))
474 eda_renderer_draw (renderer, (OBJECT *) iter->data);
475 for (iter = (GList *) contents; iter != NULL; iter = g_list_next (iter))
476 eda_renderer_draw_cues (renderer, (OBJECT *) iter->data);
479 static void
480 export_png (void)
482 cairo_surface_t *surface;
483 cairo_t *cr;
484 cairo_matrix_t mtx;
485 cairo_rectangle_t extents;
486 cairo_status_t status;
487 double scale;
489 /* Create a dummy context to permit calculating extents taking text
490 * into account. */
491 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
492 cr = cairo_create (surface);
493 cairo_surface_destroy (surface);
495 g_object_set (renderer,
496 "cairo-context", cr,
497 "render-flags", EDA_RENDERER_FLAG_HINTING,
498 NULL);
500 /* Calculate page layout */
501 export_layout_page (NULL, &extents, &mtx);
502 cairo_destroy (cr);
504 /* Create a rendering surface of the correct size. 'extents' is
505 * measured in points, so we need to use the DPI setting to
506 * transform to pixels. */
507 scale = settings.dpi / 72.0;
508 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
509 (int) ceil (extents.width * scale),
510 (int) ceil (extents.height * scale));
512 /* Create a cairo context and set the transformation matrix. */
513 cr = cairo_create (surface);
514 cairo_scale (cr, scale, scale);
515 cairo_transform (cr, &mtx);
517 /* Set up renderer. We need to enable subpixel hinting. */
518 g_object_set (renderer, "cairo-context", cr, NULL);
520 /* Draw */
521 export_draw_page (NULL);
522 export_cairo_check_error (cairo_surface_status (surface));
524 /* Save to file */
525 status = cairo_surface_write_to_png (surface, settings.outfile);
526 export_cairo_check_error (status);
529 /* Worker function used by both export_ps and export_eps */
530 static void
531 export_postscript (gboolean is_eps)
533 cairo_surface_t *surface;
534 cairo_rectangle_t extents;
535 cairo_matrix_t mtx;
536 cairo_t *cr;
537 GList *iter;
539 /* Create a surface. To begin with, we don't know the size. */
540 surface = cairo_ps_surface_create (settings.outfile, 1, 1);
541 cairo_ps_surface_set_eps (surface, is_eps);
542 cr = cairo_create (surface);
543 g_object_set (renderer, "cairo-context", cr, NULL);
545 for (iter = geda_list_get_glist (toplevel->pages);
546 iter != NULL;
547 iter = g_list_next (iter)) {
548 PAGE *page = (PAGE *) iter->data;
550 export_layout_page (page, &extents, &mtx);
551 cairo_ps_surface_set_size (surface, extents.width, extents.height);
552 cairo_set_matrix (cr, &mtx);
553 export_draw_page (page);
554 cairo_show_page (cr);
557 cairo_surface_finish (surface);
558 export_cairo_check_error (cairo_surface_status (surface));
561 static void
562 export_ps (void)
564 export_postscript (FALSE);
567 static void
568 export_eps (void)
570 export_postscript (TRUE);
573 static void
574 export_pdf (void)
576 cairo_surface_t *surface;
577 cairo_rectangle_t extents;
578 cairo_matrix_t mtx;
579 cairo_t *cr;
580 GList *iter;
582 /* Create a surface. To begin with, we don't know the size. */
583 surface = cairo_pdf_surface_create (settings.outfile, 1, 1);
584 cr = cairo_create (surface);
585 g_object_set (renderer, "cairo-context", cr, NULL);
587 for (iter = geda_list_get_glist (toplevel->pages);
588 iter != NULL;
589 iter = g_list_next (iter)) {
590 PAGE *page = (PAGE *) iter->data;
592 export_layout_page (page, &extents, &mtx);
593 cairo_pdf_surface_set_size (surface, extents.width, extents.height);
594 cairo_set_matrix (cr, &mtx);
595 export_draw_page (page);
596 cairo_show_page (cr);
599 cairo_surface_finish (surface);
600 export_cairo_check_error (cairo_surface_status (surface));
603 static void
604 export_svg ()
606 cairo_surface_t *surface;
607 cairo_rectangle_t extents;
608 cairo_matrix_t mtx;
609 cairo_t *cr;
611 /* Create a surface. To begin with, we don't know the size. */
612 surface = cairo_svg_surface_create (settings.outfile, 1, 1);
613 cr = cairo_create (surface);
614 g_object_set (renderer, "cairo-context", cr, NULL);
616 export_layout_page (NULL, &extents, &mtx);
617 cairo_pdf_surface_set_size (surface, extents.width, extents.height);
618 cairo_set_matrix (cr, &mtx);
619 export_draw_page (NULL);
621 cairo_surface_finish (surface);
622 export_cairo_check_error (cairo_surface_status (surface));
625 /* Parse a distance specification. A distance specification consists
626 * of a floating point value followed by an optional two-character
627 * unit name (in, cm, mm, pc, px, or pt, same as CSS). If no unit is
628 * specified, assumes that the unit is pt. This is used for the
629 * --margins, --size and --scale command-line options. */
630 static gdouble
631 export_parse_dist (const gchar *dist)
633 gdouble base, mult;
634 gchar *unit;
635 errno = 0;
636 base = strtod(dist, &unit);
638 if (errno != 0) return -1;
640 if (g_strcmp0 (unit, "in") == 0) {
641 mult = 72.0;
642 } else if (g_strcmp0 (unit, "cm") == 0) {
643 mult = 72.0 / 2.54;
644 } else if (g_strcmp0 (unit, "mm") == 0) {
645 mult = 72.0 / 25.4;
646 } else if (g_strcmp0 (unit, "pc") == 0) { /* Picas */
647 mult = 12.0;
648 } else if (g_strcmp0 (unit, "px") == 0) {
649 mult = 72.0 / settings.dpi;
650 } else if (g_strcmp0 (unit, "pt") == 0
651 || unit[0] == 0) {
652 mult = 1.0;
653 } else {
654 return -1; /* Indicate that parsing unit failed */
657 return mult * base;
660 /* Parse the --align command line option. */
661 static gboolean
662 export_parse_align (const gchar *align)
664 int n;
665 gchar **args;
667 /* Automatic alignment case */
668 if (g_strcmp0 (align, "auto") == 0 || align[0] == 0) {
669 settings.align[0] = settings.align[1] = 0.5;
670 return TRUE;
673 args = g_strsplit_set (align, ":; ", 2);
674 for (n = 0; args[n] != NULL; n++) {
675 gdouble d = strtod (args[n], NULL);
676 if (d < 0 || d > 1) return FALSE;
677 settings.align[n] = d;
679 g_strfreev (args);
681 if (n != 2) return FALSE;
682 return TRUE;
685 /* Parse the --layout command line option and the export.layout config
686 * file setting. */
687 static gboolean
688 export_parse_layout (const gchar *layout)
690 if (g_strcmp0 (layout, "landscape") == 0) {
691 settings.layout = ORIENTATION_LANDSCAPE;
692 } else if (g_strcmp0 (layout, "portrait") == 0) {
693 settings.layout = ORIENTATION_PORTRAIT;
694 } else if (g_strcmp0 (layout, "auto") == 0
695 || layout == NULL
696 || layout[0] == 0) {
697 settings.layout = ORIENTATION_AUTO;
698 } else {
699 return FALSE;
701 return TRUE;
704 /* Parse the --margins command-line option. If the value is "auto" or
705 * empty, sets margins to be determined automatically from paper size
706 * or compiled-in defaults. Otherwise, expects a list of 1-4 distance
707 * specs; see export_parse_dist(). Rules if <4 distances are
708 * specified are as for 'margin' property in CSS. */
709 static gboolean
710 export_parse_margins (const gchar *margins)
712 gint n;
713 gchar **dists;
715 g_assert (margins != NULL);
717 /* Automatic margins case */
718 if (g_strcmp0 (margins, "auto") == 0 || margins[0] == 0) {
719 for (n = 0; n < 4; n++) settings.margins[n] = -1;
720 return TRUE;
723 dists = g_strsplit_set (margins, ":; ", 4);
724 for (n = 0; dists[n] != NULL; n++) {
725 gdouble d = export_parse_dist (dists[n]);
726 if (d < 0) return FALSE;
727 settings.margins[n] = d;
729 g_strfreev (dists);
731 if (n == 1) {
732 /* If only one value is specified, it applies to all four sides. */
733 settings.margins[3] = settings.margins[2]
734 = settings.margins[1] = settings.margins[0];
735 } else if (n == 2) {
736 /* If two values are specified, the first applies to the
737 top/bottom, and the second to left/right. */
738 settings.margins[2] = settings.margins[0];
739 settings.margins[3] = settings.margins[1];
740 } else if (n == 3) {
741 /* If three values are specified, the first applies to the top,
742 the second to left/right, and the third to the bottom. */
743 settings.margins[3] = settings.margins[1];
744 } else if (n != 4) {
745 return FALSE; /* Must correctly specify 1-4 distances + units */
748 return TRUE;
751 /* Parse the --paper option. Clears any size setting. */
752 static gboolean
753 export_parse_paper (const gchar *paper)
755 GtkPaperSize *paper_size = gtk_paper_size_new (paper);
756 if (paper_size == NULL) return FALSE;
758 if (settings.paper != NULL) gtk_paper_size_free (settings.paper);
759 settings.paper = paper_size;
760 /* Must reset size setting to invalid or it will override paper
761 * setting */
762 settings.size[0] = settings.size[1] = -1;
763 return TRUE;
766 /* Parse the --size option, which must either be "auto" (i.e. obtain
767 * size from drawing) or a list of two distances (width/height). */
768 static gboolean
769 export_parse_size (const gchar *size)
771 gint n;
772 gchar **dists;
774 /* Automatic size case */
775 if (g_strcmp0 (size, "auto") == 0 || size[0] == 0) {
776 settings.size[0] = settings.size[1] = -1;
777 return TRUE;
780 dists = g_strsplit_set (size, ":; ", 2);
781 for (n = 0; dists[n] != NULL; n++) {
782 gdouble d = export_parse_dist (dists[n]);
783 if (d < 0) return FALSE;
784 settings.size[n] = d;
786 g_strfreev (dists);
787 if (n != 2) return FALSE;
789 return TRUE;
792 /* Parse the --scale option. The value should be a distance
793 * corresponding to 100 points in gschem (1 default grid spacing). */
794 static gboolean
795 export_parse_scale (const gchar *scale)
797 gdouble d = export_parse_dist (scale);
798 if (d <= 0) return FALSE;
799 settings.scale = d/100;
800 return TRUE;
803 /* Initialise settings from config store. */
804 static void
805 export_config (void)
807 EdaConfig *cfg = eda_config_get_context_for_file (NULL);
808 gchar *str;
809 gdouble *lst;
810 gdouble dval;
811 gdouble bval;
812 gsize n;
813 GError *err = NULL;
815 /* Parse orientation */
816 str = eda_config_get_string (cfg, "export", "layout", NULL);
817 export_parse_layout (str); /* Don't care if it works */
818 g_free (str);
820 /* Parse paper size */
821 str = eda_config_get_string (cfg, "export", "paper", NULL);
822 export_parse_paper (str);
823 g_free (str);
825 /* Parse specific size setting -- always in points */
826 if (eda_config_has_key (cfg, "export", "size", NULL)) {
827 lst = eda_config_get_double_list (cfg, "export", "size", &n, NULL);
828 if (lst != NULL) {
829 if (n >= 2) {
830 memcpy (settings.size, lst, 2*sizeof(gdouble));
832 g_free (lst);
834 /* Since a specific size was provided, ditch the paper size
835 * setting */
836 if (settings.paper != NULL) {
837 gtk_paper_size_free (settings.paper);
838 settings.paper = NULL;
842 /* Parse margins -- always in points */
843 lst = eda_config_get_double_list (cfg, "export", "margins", &n, NULL);
844 if (lst != NULL) {
845 if (n >= 4) { /* In the config file all four sides must be specified */
846 memcpy (settings.margins, lst, 4*sizeof(gdouble));
848 g_free (lst);
851 /* Parse alignment */
852 lst = eda_config_get_double_list (cfg, "export", "align", &n, NULL);
853 if (lst != NULL) {
854 if (n >= 2) { /* Both halign and valign must be specified */
855 memcpy (settings.align, lst, 2*sizeof(gdouble));
857 g_free (lst);
860 /* Parse dpi */
861 dval = eda_config_get_double (cfg, "export", "dpi", &err);
862 if (err == NULL) {
863 settings.dpi = dval;
864 } else {
865 g_clear_error (&err);
868 bval = eda_config_get_boolean (cfg, "export", "monochrome", &err);
869 if (err == NULL) {
870 settings.color = !bval;
871 } else {
872 g_clear_error (&err);
875 str = eda_config_get_string (cfg, "export", "font", NULL);
876 if (str != NULL) {
877 g_free (settings.font);
878 settings.font = str;
882 #define export_short_options "a:cd:f:F:hl:m:o:p:s:k:"
884 static struct option export_long_options[] = {
885 {"no-color", 0, NULL, 2},
886 {"align", 1, NULL, 'a'},
887 {"color", 0, NULL, 'c'},
888 {"dpi", 1, NULL, 'd'},
889 {"format", 1, NULL, 'f'},
890 {"font", 1, NULL, 'F'},
891 {"help", 0, NULL, 'h'},
892 {"layout", 0, NULL, 'l'},
893 {"margins", 1, NULL, 'm'},
894 {"output", 1, NULL, 'o'},
895 {"paper", 1, NULL, 'p'},
896 {"size", 1, NULL, 's'},
897 {"scale", 1, NULL, 'k'},
898 {NULL, 0, NULL, 0},
901 static void
902 export_usage (void)
904 printf (_("Usage: gaf export [OPTION ...] -o OUTPUT [--] FILE ...\n"
905 "\n"
906 "Export gEDA files in various image formats.\n"
907 "\n"
908 " -f, --format=TYPE output format (normally autodetected)\n"
909 " -o, --output=OUTPUT output filename\n"
910 " -p, --paper=NAME select paper size by name\n"
911 " -s, --size=WIDTH;HEIGHT specify exact paper size\n"
912 " -k, --scale=FACTOR specify output scale factor\n"
913 " -l, --layout=ORIENT page orientation\n"
914 " -m, --margins=TOP;LEFT;BOTTOM;RIGHT\n"
915 " set page margins\n"
916 " -a, --align=HALIGN;VALIGN\n"
917 " set alignment of drawing within page\n"
918 " -d, --dpi=DPI pixels-per-inch for raster outputs\n"
919 " -c, --color enable color output\n"
920 " --no-color disable color output\n"
921 " -F, --font=NAME set font family for printing text\n"
922 " -h, --help display usage information and exit\n"
923 "\n"
924 "Please report bugs to %s.\n"),
925 PACKAGE_BUGREPORT);
926 exit (0);
929 /* Helper function for checking that a command-line option value can
930 * be successfully converted to UTF-8. */
931 static inline gchar *
932 export_command_line__utf8_check (gchar *str, gchar *arg)
934 GError *err = NULL;
935 gchar *result;
937 g_assert (str != NULL);
938 g_assert (arg != NULL);
939 result = g_locale_to_utf8 (str, -1, NULL, NULL, &err);
940 if (result == NULL) {
941 fprintf (stderr, bad_arg_msg, optarg, arg);
942 fprintf (stderr, see_help_msg);
943 exit (1);
945 return result;
948 static void
949 export_command_line (int argc, char * const *argv)
951 int c;
952 gchar *str;
954 /* Parse command-line arguments */
955 while ((c = getopt_long (argc, argv, export_short_options,
956 export_long_options, NULL)) != -1) {
957 switch (c) {
958 case 0:
959 /* This is a long-form-only flag option, and has already been
960 * dealt with by getopt_long(). */
961 break;
963 case 2: /* --no-color */
964 settings.color = FALSE;
965 break;
967 case 'a':
968 str = export_command_line__utf8_check (optarg, "-a,--align");
969 if (!export_parse_align (str)) {
970 fprintf (stderr, bad_arg_msg, optarg, "-a,--align");
971 fprintf (stderr, see_help_msg);
972 exit (1);
974 g_free (str);
975 break;
977 case 'c':
978 settings.color = TRUE;
979 break;
981 case 'd':
982 settings.dpi = strtod (optarg, NULL);
983 if (settings.dpi <= 0) {
984 fprintf (stderr, bad_arg_msg, optarg, "-d,--dpi");
985 fprintf (stderr, see_help_msg);
986 exit (1);
988 break;
990 case 'f':
991 g_free (settings.format);
992 settings.format = export_command_line__utf8_check (optarg, "-f,--format");
993 break;
995 case 'F':
996 str = export_command_line__utf8_check (optarg, "-F,--font");
997 g_free (settings.font);
998 settings.font = str;
999 break;
1001 case 'h':
1002 export_usage ();
1003 break;
1005 case 'k':
1006 str = export_command_line__utf8_check (optarg, "-k,--scale");
1007 if (!export_parse_scale (str)) {
1008 fprintf (stderr, bad_arg_msg, optarg, "-k,--scale");
1009 fprintf (stderr, see_help_msg);
1010 exit (1);
1012 g_free (str);
1013 /* Since a specific scale was provided, ditch the paper size
1014 * setting */
1015 if (settings.paper != NULL) {
1016 gtk_paper_size_free (settings.paper);
1017 settings.paper = NULL;
1019 break;
1021 case 'l':
1022 if (!export_parse_layout (optarg)) {
1023 fprintf (stderr, bad_arg_msg,
1024 optarg, "-l,--layout");
1025 fprintf (stderr, see_help_msg);
1026 exit (1);
1028 break;
1030 case 'm':
1031 str = export_command_line__utf8_check (optarg, "-m,--margins");
1032 if (!export_parse_margins (str)) {
1033 fprintf (stderr, bad_arg_msg, optarg, "-m,--margins");
1034 fprintf (stderr, see_help_msg);
1035 exit (1);
1037 g_free (str);
1038 break;
1040 case 'o':
1041 settings.outfile = optarg;
1042 break;
1044 case 'p':
1045 str = export_command_line__utf8_check (optarg, "-p,--paper");
1046 if (!export_parse_paper (str)) {
1047 fprintf (stderr, bad_arg_msg, optarg, "-p,--paper");
1048 fprintf (stderr, see_help_msg);
1049 exit (1);
1051 g_free (str);
1052 break;
1054 case 's':
1055 str = export_command_line__utf8_check (optarg, "-s,--size");
1056 if (!export_parse_size (str)) {
1057 fprintf (stderr, bad_arg_msg, optarg, "-s,--size");
1058 fprintf (stderr, see_help_msg);
1059 exit (1);
1061 g_free (str);
1062 /* Since a specific size was provided, ditch the paper size
1063 * setting */
1064 if (settings.paper != NULL) {
1065 gtk_paper_size_free (settings.paper);
1066 settings.paper = NULL;
1068 break;
1070 case '?':
1071 /* getopt_long already printed an error message */
1072 fprintf (stderr, see_help_msg);
1073 exit (1);
1074 break;
1075 default:
1076 g_assert_not_reached ();
1080 /* Check that some schematic files to print were provided */
1081 if (argc <= optind) {
1082 fprintf (stderr,
1083 _("ERROR: You must specify at least one input filename.\n"));
1084 fprintf (stderr, see_help_msg);
1085 exit (1);
1087 settings.infilec = argc - optind;
1088 settings.infilev = &argv[optind];
1090 if (settings.outfile == NULL) {
1091 fprintf (stderr,
1092 _("ERROR: You must specify an output filename.\n"));
1093 fprintf (stderr, see_help_msg);
1094 exit (1);