1 #define __MAIN_C__
3 /** \file
4 * Inkscape - an ambitious vector drawing program
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Frank Felfe <innerspace@iname.com>
9 * Davide Puricelli <evo@debian.org>
10 * Mitsuru Oka <oka326@parkcity.ne.jp>
11 * Masatake YAMATO <jet@gyve.org>
12 * F.J.Franklin <F.J.Franklin@sheffield.ac.uk>
13 * Michael Meeks <michael@helixcode.com>
14 * Chema Celorio <chema@celorio.com>
15 * Pawel Palucha
16 * Bryce Harrington <bryce@bryceharrington.com>
17 * ... and various people who have worked with various projects
18 *
19 * Copyright (C) 1999-2004 authors
20 * Copyright (C) 2001-2002 Ximian, Inc.
21 *
22 * Released under GNU GPL, read the file 'COPYING' for more information
23 */
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29 #include "path-prefix.h"
31 // This has to be included prior to anything that includes setjmp.h, it croaks otherwise
32 #include <png.h>
34 #include <gtk/gtkmessagedialog.h>
36 #ifdef HAVE_IEEEFP_H
37 #include <ieeefp.h>
38 #endif
39 #include <cstring>
40 #include <string>
41 #include <locale.h>
42 #include <stdlib.h>
44 #include <popt.h>
45 #ifndef POPT_TABLEEND
46 #define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }
47 #endif /* Not def: POPT_TABLEEND */
49 #include <libxml/tree.h>
50 #include <glib.h>
51 #include <glib/gprintf.h>
52 #include <glib-object.h>
53 #include <gtk/gtk.h>
54 #include <gtk/gtkmain.h>
55 #include <gtk/gtksignal.h>
56 #include <gtk/gtkwindow.h>
57 #include <gtk/gtkbox.h>
59 #include "gc-core.h"
61 #ifdef AND
62 #undef AND
63 #endif
65 #include "macros.h"
66 #include "file.h"
67 #include "document.h"
68 #include "sp-object.h"
69 #include "interface.h"
70 #include "print.h"
71 #include "color.h"
72 #include "sp-item.h"
73 #include "sp-root.h"
74 #include "unit-constants.h"
76 #include "svg/svg.h"
77 #include "svg/svg-color.h"
78 #include "svg/stringstream.h"
80 #include "inkscape-private.h"
81 #include "inkscape-version.h"
83 #include "sp-namedview.h"
84 #include "sp-guide.h"
85 #include "sp-object-repr.h"
86 #include "xml/repr.h"
88 #include "io/sys.h"
90 #include "debug/logger.h"
91 #include "debug/log-display-config.h"
93 #include "helper/png-write.h"
94 #include "helper/geom.h"
96 #include <extension/extension.h>
97 #include <extension/system.h>
98 #include <extension/db.h>
99 #include <extension/output.h>
100 #include <extension/input.h>
102 #ifdef WIN32
103 #include "registrytool.h"
104 #include "extension/internal/win32.h"
105 using Inkscape::Extension::Internal::PrintWin32;
106 #endif // WIN32
108 #include "extension/init.h"
110 #include <glibmm/i18n.h>
111 #include <gtkmm/main.h>
113 #ifndef HAVE_BIND_TEXTDOMAIN_CODESET
114 #define bind_textdomain_codeset(p,c)
115 #endif
117 #include "application/application.h"
118 #include "main-cmdlineact.h"
119 #include "widgets/icon.h"
120 #include "ui/widget/panel.h"
122 #include <errno.h>
124 enum {
125 SP_ARG_NONE,
126 SP_ARG_NOGUI,
127 SP_ARG_GUI,
128 SP_ARG_FILE,
129 SP_ARG_PRINT,
130 SP_ARG_EXPORT_PNG,
131 SP_ARG_EXPORT_DPI,
132 SP_ARG_EXPORT_AREA,
133 SP_ARG_EXPORT_AREA_DRAWING,
134 SP_ARG_EXPORT_AREA_PAGE,
135 SP_ARG_EXPORT_AREA_SNAP,
136 SP_ARG_EXPORT_WIDTH,
137 SP_ARG_EXPORT_HEIGHT,
138 SP_ARG_EXPORT_ID,
139 SP_ARG_EXPORT_ID_ONLY,
140 SP_ARG_EXPORT_USE_HINTS,
141 SP_ARG_EXPORT_BACKGROUND,
142 SP_ARG_EXPORT_BACKGROUND_OPACITY,
143 SP_ARG_EXPORT_SVG,
144 SP_ARG_EXPORT_PS,
145 SP_ARG_EXPORT_EPS,
146 SP_ARG_EXPORT_PDF,
147 SP_ARG_EXPORT_LATEX,
148 #ifdef WIN32
149 SP_ARG_EXPORT_EMF,
150 #endif //WIN32
151 SP_ARG_EXPORT_TEXT_TO_PATH,
152 SP_ARG_EXPORT_IGNORE_FILTERS,
153 SP_ARG_EXTENSIONDIR,
154 SP_ARG_QUERY_X,
155 SP_ARG_QUERY_Y,
156 SP_ARG_QUERY_WIDTH,
157 SP_ARG_QUERY_HEIGHT,
158 SP_ARG_QUERY_ALL,
159 SP_ARG_QUERY_ID,
160 SP_ARG_SHELL,
161 SP_ARG_VERSION,
162 SP_ARG_VACUUM_DEFS,
163 SP_ARG_VERB_LIST,
164 SP_ARG_VERB,
165 SP_ARG_SELECT,
166 SP_ARG_LAST
167 };
169 int sp_main_gui(int argc, char const **argv);
170 int sp_main_console(int argc, char const **argv);
171 static void sp_do_export_png(SPDocument *doc);
172 static void do_export_ps_pdf(SPDocument* doc, gchar const* uri, char const *mime);
173 #ifdef WIN32
174 static void do_export_emf(SPDocument* doc, gchar const* uri, char const *mime);
175 #endif //WIN32
176 static void do_query_dimension (SPDocument *doc, bool extent, Geom::Dim2 const axis, const gchar *id);
177 static void do_query_all (SPDocument *doc);
178 static void do_query_all_recurse (SPObject *o);
180 static gchar *sp_global_printer = NULL;
181 static gchar *sp_export_png = NULL;
182 static gchar *sp_export_dpi = NULL;
183 static gchar *sp_export_area = NULL;
184 static gboolean sp_export_area_drawing = FALSE;
185 static gboolean sp_export_area_page = FALSE;
186 static gboolean sp_export_latex = FALSE;
187 static gchar *sp_export_width = NULL;
188 static gchar *sp_export_height = NULL;
189 static gchar *sp_export_id = NULL;
190 static gchar *sp_export_background = NULL;
191 static gchar *sp_export_background_opacity = NULL;
192 static gboolean sp_export_area_snap = FALSE;
193 static gboolean sp_export_use_hints = FALSE;
194 static gboolean sp_export_id_only = FALSE;
195 static gchar *sp_export_svg = NULL;
196 static gchar *sp_export_ps = NULL;
197 static gchar *sp_export_eps = NULL;
198 static gchar *sp_export_pdf = NULL;
199 #ifdef WIN32
200 static gchar *sp_export_emf = NULL;
201 #endif //WIN32
202 static gboolean sp_export_text_to_path = FALSE;
203 static gboolean sp_export_ignore_filters = FALSE;
204 static gboolean sp_export_font = FALSE;
205 static gboolean sp_query_x = FALSE;
206 static gboolean sp_query_y = FALSE;
207 static gboolean sp_query_width = FALSE;
208 static gboolean sp_query_height = FALSE;
209 static gboolean sp_query_all = FALSE;
210 static gchar *sp_query_id = NULL;
211 static int sp_new_gui = FALSE;
212 static gboolean sp_shell = FALSE;
213 static gboolean sp_vacuum_defs = FALSE;
215 static gchar *sp_export_png_utf8 = NULL;
216 static gchar *sp_export_svg_utf8 = NULL;
217 static gchar *sp_global_printer_utf8 = NULL;
220 /**
221 * Reset variables to default values.
222 */
223 static void resetCommandlineGlobals() {
224 sp_global_printer = NULL;
225 sp_export_png = NULL;
226 sp_export_dpi = NULL;
227 sp_export_area = NULL;
228 sp_export_area_drawing = FALSE;
229 sp_export_area_page = FALSE;
230 sp_export_latex = FALSE;
231 sp_export_width = NULL;
232 sp_export_height = NULL;
233 sp_export_id = NULL;
234 sp_export_background = NULL;
235 sp_export_background_opacity = NULL;
236 sp_export_area_snap = FALSE;
237 sp_export_use_hints = FALSE;
238 sp_export_id_only = FALSE;
239 sp_export_svg = NULL;
240 sp_export_ps = NULL;
241 sp_export_eps = NULL;
242 sp_export_pdf = NULL;
243 #ifdef WIN32
244 sp_export_emf = NULL;
245 #endif //WIN32
246 sp_export_text_to_path = FALSE;
247 sp_export_ignore_filters = FALSE;
248 sp_export_font = FALSE;
249 sp_query_x = FALSE;
250 sp_query_y = FALSE;
251 sp_query_width = FALSE;
252 sp_query_height = FALSE;
253 sp_query_all = FALSE;
254 sp_query_id = NULL;
255 sp_vacuum_defs = FALSE;
257 sp_export_png_utf8 = NULL;
258 sp_export_svg_utf8 = NULL;
259 sp_global_printer_utf8 = NULL;
260 }
262 #ifdef WIN32
263 static bool replaceArgs( int& argc, char**& argv );
264 #endif
265 static GSList *sp_process_args(poptContext ctx);
266 struct poptOption options[] = {
267 {"version", 'V',
268 POPT_ARG_NONE, NULL, SP_ARG_VERSION,
269 N_("Print the Inkscape version number"),
270 NULL},
272 {"without-gui", 'z',
273 POPT_ARG_NONE, NULL, SP_ARG_NOGUI,
274 N_("Do not use X server (only process files from console)"),
275 NULL},
277 {"with-gui", 'g',
278 POPT_ARG_NONE, NULL, SP_ARG_GUI,
279 N_("Try to use X server (even if $DISPLAY is not set)"),
280 NULL},
282 {"file", 'f',
283 POPT_ARG_STRING, NULL, SP_ARG_FILE,
284 N_("Open specified document(s) (option string may be excluded)"),
285 N_("FILENAME")},
287 {"print", 'p',
288 POPT_ARG_STRING, &sp_global_printer, SP_ARG_PRINT,
289 N_("Print document(s) to specified output file (use '| program' for pipe)"),
290 N_("FILENAME")},
292 {"export-png", 'e',
293 POPT_ARG_STRING, &sp_export_png, SP_ARG_EXPORT_PNG,
294 N_("Export document to a PNG file"),
295 N_("FILENAME")},
297 {"export-dpi", 'd',
298 POPT_ARG_STRING, &sp_export_dpi, SP_ARG_EXPORT_DPI,
299 N_("Resolution for exporting to bitmap and for rasterization of filters in PS/EPS/PDF (default 90)"),
300 N_("DPI")},
302 {"export-area", 'a',
303 POPT_ARG_STRING, &sp_export_area, SP_ARG_EXPORT_AREA,
304 N_("Exported area in SVG user units (default is the page; 0,0 is lower-left corner)"),
305 N_("x0:y0:x1:y1")},
307 {"export-area-drawing", 'D',
308 POPT_ARG_NONE, &sp_export_area_drawing, SP_ARG_EXPORT_AREA_DRAWING,
309 N_("Exported area is the entire drawing (not page)"),
310 NULL},
312 {"export-area-page", 'C',
313 POPT_ARG_NONE, &sp_export_area_page, SP_ARG_EXPORT_AREA_PAGE,
314 N_("Exported area is the entire page"),
315 NULL},
317 {"export-area-snap", 0,
318 POPT_ARG_NONE, &sp_export_area_snap, SP_ARG_EXPORT_AREA_SNAP,
319 N_("Snap the bitmap export area outwards to the nearest integer values (in SVG user units)"),
320 NULL},
322 {"export-width", 'w',
323 POPT_ARG_STRING, &sp_export_width, SP_ARG_EXPORT_WIDTH,
324 N_("The width of exported bitmap in pixels (overrides export-dpi)"),
325 N_("WIDTH")},
327 {"export-height", 'h',
328 POPT_ARG_STRING, &sp_export_height, SP_ARG_EXPORT_HEIGHT,
329 N_("The height of exported bitmap in pixels (overrides export-dpi)"),
330 N_("HEIGHT")},
332 {"export-id", 'i',
333 POPT_ARG_STRING, &sp_export_id, SP_ARG_EXPORT_ID,
334 N_("The ID of the object to export"),
335 N_("ID")},
337 {"export-id-only", 'j',
338 POPT_ARG_NONE, &sp_export_id_only, SP_ARG_EXPORT_ID_ONLY,
339 // TRANSLATORS: this means: "Only export the object whose id is given in --export-id".
340 // See "man inkscape" for details.
341 N_("Export just the object with export-id, hide all others (only with export-id)"),
342 NULL},
344 {"export-use-hints", 't',
345 POPT_ARG_NONE, &sp_export_use_hints, SP_ARG_EXPORT_USE_HINTS,
346 N_("Use stored filename and DPI hints when exporting (only with export-id)"),
347 NULL},
349 {"export-background", 'b',
350 POPT_ARG_STRING, &sp_export_background, SP_ARG_EXPORT_BACKGROUND,
351 N_("Background color of exported bitmap (any SVG-supported color string)"),
352 N_("COLOR")},
354 {"export-background-opacity", 'y',
355 POPT_ARG_STRING, &sp_export_background_opacity, SP_ARG_EXPORT_BACKGROUND_OPACITY,
356 N_("Background opacity of exported bitmap (either 0.0 to 1.0, or 1 to 255)"),
357 N_("VALUE")},
359 {"export-plain-svg", 'l',
360 POPT_ARG_STRING, &sp_export_svg, SP_ARG_EXPORT_SVG,
361 N_("Export document to plain SVG file (no sodipodi or inkscape namespaces)"),
362 N_("FILENAME")},
364 {"export-ps", 'P',
365 POPT_ARG_STRING, &sp_export_ps, SP_ARG_EXPORT_PS,
366 N_("Export document to a PS file"),
367 N_("FILENAME")},
369 {"export-eps", 'E',
370 POPT_ARG_STRING, &sp_export_eps, SP_ARG_EXPORT_EPS,
371 N_("Export document to an EPS file"),
372 N_("FILENAME")},
374 {"export-pdf", 'A',
375 POPT_ARG_STRING, &sp_export_pdf, SP_ARG_EXPORT_PDF,
376 N_("Export document to a PDF file"),
377 N_("FILENAME")},
379 {"export-latex", 0,
380 POPT_ARG_NONE, &sp_export_latex, SP_ARG_EXPORT_LATEX,
381 N_("Export PDF/PS/EPS without text. Besides the PDF/PS/EPS, a LaTeX file is exported, putting the text on top of the PDF/PS/EPS file. Include the result in LaTeX like: \\input{latexfile.tex}"),
382 NULL},
384 #ifdef WIN32
385 {"export-emf", 'M',
386 POPT_ARG_STRING, &sp_export_emf, SP_ARG_EXPORT_EMF,
387 N_("Export document to an Enhanced Metafile (EMF) File"),
388 N_("FILENAME")},
389 #endif //WIN32
391 {"export-text-to-path", 'T',
392 POPT_ARG_NONE, &sp_export_text_to_path, SP_ARG_EXPORT_TEXT_TO_PATH,
393 N_("Convert text object to paths on export (PS, EPS, PDF)"),
394 NULL},
396 {"export-ignore-filters", 0,
397 POPT_ARG_NONE, &sp_export_ignore_filters, SP_ARG_EXPORT_IGNORE_FILTERS,
398 N_("Render filtered objects without filters, instead of rasterizing (PS, EPS, PDF)"),
399 NULL},
401 {"query-x", 'X',
402 POPT_ARG_NONE, &sp_query_x, SP_ARG_QUERY_X,
403 // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help"
404 N_("Query the X coordinate of the drawing or, if specified, of the object with --query-id"),
405 NULL},
407 {"query-y", 'Y',
408 POPT_ARG_NONE, &sp_query_y, SP_ARG_QUERY_Y,
409 // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help"
410 N_("Query the Y coordinate of the drawing or, if specified, of the object with --query-id"),
411 NULL},
413 {"query-width", 'W',
414 POPT_ARG_NONE, &sp_query_width, SP_ARG_QUERY_WIDTH,
415 // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help"
416 N_("Query the width of the drawing or, if specified, of the object with --query-id"),
417 NULL},
419 {"query-height", 'H',
420 POPT_ARG_NONE, &sp_query_height, SP_ARG_QUERY_HEIGHT,
421 // TRANSLATORS: "--query-id" is an Inkscape command line option; see "inkscape --help"
422 N_("Query the height of the drawing or, if specified, of the object with --query-id"),
423 NULL},
425 {"query-all", 'S',
426 POPT_ARG_NONE, &sp_query_all, SP_ARG_QUERY_ALL,
427 N_("List id,x,y,w,h for all objects"),
428 NULL},
430 {"query-id", 'I',
431 POPT_ARG_STRING, &sp_query_id, SP_ARG_QUERY_ID,
432 N_("The ID of the object whose dimensions are queried"),
433 N_("ID")},
435 {"extension-directory", 'x',
436 POPT_ARG_NONE, NULL, SP_ARG_EXTENSIONDIR,
437 // TRANSLATORS: this option makes Inkscape print the name (path) of the extension directory
438 N_("Print out the extension directory and exit"),
439 NULL},
441 {"vacuum-defs", 0,
442 POPT_ARG_NONE, &sp_vacuum_defs, SP_ARG_VACUUM_DEFS,
443 N_("Remove unused definitions from the defs section(s) of the document"),
444 NULL},
446 {"verb-list", 0,
447 POPT_ARG_NONE, NULL, SP_ARG_VERB_LIST,
448 N_("List the IDs of all the verbs in Inkscape"),
449 NULL},
451 {"verb", 0,
452 POPT_ARG_STRING, NULL, SP_ARG_VERB,
453 N_("Verb to call when Inkscape opens."),
454 N_("VERB-ID")},
456 {"select", 0,
457 POPT_ARG_STRING, NULL, SP_ARG_SELECT,
458 N_("Object ID to select when Inkscape opens."),
459 N_("OBJECT-ID")},
461 {"shell", 0,
462 POPT_ARG_NONE, &sp_shell, SP_ARG_SHELL,
463 N_("Start Inkscape in interactive shell mode."),
464 NULL},
466 POPT_AUTOHELP POPT_TABLEEND
467 };
469 static bool needToRecodeParams = true;
470 gchar * blankParam = g_strdup("");
474 #ifdef WIN32
476 /**
477 * Set up the PATH and PYTHONPATH environment variables on Windows
478 * @param exe Inkscape executable directory in UTF-8
479 */
480 static void _win32_set_inkscape_env(gchar const *exe)
481 {
482 gchar const *path = g_getenv("PATH");
483 gchar const *pythonpath = g_getenv("PYTHONPATH");
485 gchar *python = g_build_filename(exe, "python", NULL);
486 gchar *scripts = g_build_filename(exe, "python", "Scripts", NULL);
487 gchar *perl = g_build_filename(exe, "python", NULL);
488 gchar *pythonlib = g_build_filename(exe, "python", "Lib", NULL);
489 gchar *pythondll = g_build_filename(exe, "python", "DLLs", NULL);
490 gchar *extdir = g_build_filename(exe, "share", "extensions", NULL);
492 // Python 2.x needs short paths in PYTHONPATH.
493 // Otherwise it doesn't work when Inkscape is installed in Unicode directories.
494 // g_win32_locale_filename_from_utf8 is the GLib wrapper for GetShortPathName.
495 // Remove this once we move to Python 3.0.
496 gchar *python_s = g_win32_locale_filename_from_utf8(python);
497 gchar *pythonlib_s = g_win32_locale_filename_from_utf8(pythonlib);
498 gchar *pythondll_s = g_win32_locale_filename_from_utf8(pythondll);
499 gchar *extdir_s = g_win32_locale_filename_from_utf8(extdir);
501 gchar *new_path;
502 gchar *new_pythonpath;
503 if (path) {
504 new_path = g_strdup_printf("%s;%s;%s;%s;%s", exe, python, scripts, perl, path);
505 } else {
506 new_path = g_strdup_printf("%s;%s;%s;%s", exe, python, scripts, perl);
507 }
508 if (pythonpath) {
509 new_pythonpath = g_strdup_printf("%s;%s;%s;%s;%s",
510 extdir_s, python_s, pythonlib_s, pythondll_s, pythonpath);
511 } else {
512 new_pythonpath = g_strdup_printf("%s;%s;%s;%s",
513 extdir_s, python_s, pythonlib_s, pythondll_s);
514 }
516 g_setenv("PATH", new_path, TRUE);
517 g_setenv("PYTHONPATH", new_pythonpath, TRUE);
519 /*
520 printf("PATH = %s\n\n", g_getenv("PATH"));
521 printf("PYTHONPATH = %s\n\n", g_getenv("PYTHONPATH"));
523 gchar *p = g_find_program_in_path("python");
524 if (p) {
525 printf("python in %s\n\n", p);
526 g_free(p);
527 } else {
528 printf("python not found\n\n");
529 }*/
531 g_free(python);
532 g_free(scripts);
533 g_free(perl);
534 g_free(pythonlib);
535 g_free(pythondll);
537 g_free(python_s);
538 g_free(pythonlib_s);
539 g_free(pythondll_s);
541 g_free(new_path);
542 g_free(new_pythonpath);
543 }
544 #endif
546 /**
547 * This is the classic main() entry point of the program, though on some
548 * architectures it might be called by something else.
549 */
550 int
551 main(int argc, char **argv)
552 {
553 #ifdef HAVE_FPSETMASK
554 /* This is inherited from Sodipodi code, where it was in #ifdef __FreeBSD__. It's probably
555 safe to remove: the default mask is already 0 in C99, and in current FreeBSD according to
556 the fenv man page on www.freebsd.org, and in glibc according to (libc)FP Exceptions. */
557 fpsetmask(fpgetmask() & ~(FP_X_DZ | FP_X_INV));
558 #endif
560 #ifdef WIN32
561 /*
562 Set the current directory to the directory of the
563 executable. This seems redundant, but is needed for
564 when inkscape.exe is executed from another directory.
565 We use relative paths on win32.
566 HKCR\svgfile\shell\open\command is a good example
568 TODO: this breaks the CLI on Windows, see LP #167455
569 However, the CLI is broken anyway, because we are a GUI app
570 */
571 const int pathbuf = 2048;
572 gunichar2 *path = g_new(gunichar2, pathbuf);
573 GetModuleFileNameW(NULL, (WCHAR*) path, pathbuf);
574 gchar *inkscape = g_utf16_to_utf8(path, -1, NULL, NULL, NULL);
575 gchar *exedir = g_path_get_dirname(inkscape);
576 gunichar2 *dirw = g_utf8_to_utf16(exedir, -1, NULL, NULL, NULL);
577 SetCurrentDirectoryW((WCHAR*) dirw);
578 _win32_set_inkscape_env(exedir);
580 # ifdef ENABLE_NLS
581 // obtain short path to executable dir and pass it
582 // to bindtextdomain (it doesn't understand UTF-8)
583 gchar *shortexedir = g_win32_locale_filename_from_utf8(exedir);
584 gchar *localepath = g_build_filename(shortexedir, PACKAGE_LOCALE_DIR, NULL);
585 bindtextdomain(GETTEXT_PACKAGE, localepath);
586 g_free(shortexedir);
587 g_free(localepath);
588 # endif
590 g_free(path);
591 g_free(inkscape);
592 g_free(exedir);
593 g_free(dirw);
595 // Don't touch the registry (works fine without it) for Inkscape Portable
596 gchar const *val = g_getenv("INKSCAPE_PORTABLE_PROFILE_DIR");
597 if (!val) {
598 RegistryTool rt;
599 rt.setPathInfo();
600 }
601 #elif defined(ENABLE_NLS)
602 # ifdef ENABLE_BINRELOC
603 bindtextdomain(GETTEXT_PACKAGE, BR_LOCALEDIR(""));
604 # else
605 bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
606 # endif
607 #endif
609 // the bit below compiles regardless of platform
610 #ifdef ENABLE_NLS
611 // Allow the user to override the locale directory by setting
612 // the environment variable INKSCAPE_LOCALEDIR.
613 char const *inkscape_localedir = g_getenv("INKSCAPE_LOCALEDIR");
614 if (inkscape_localedir != NULL) {
615 bindtextdomain(GETTEXT_PACKAGE, inkscape_localedir);
616 }
618 // common setup
619 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
620 textdomain(GETTEXT_PACKAGE);
621 #endif
623 // Prevents errors like "Unable to wrap GdkPixbuf..." (in nr-filter-image.cpp for example)
624 Gtk::Main::init_gtkmm_internals();
626 LIBXML_TEST_VERSION
628 Inkscape::GC::init();
630 Inkscape::Debug::Logger::init();
632 gboolean use_gui;
634 #ifndef WIN32
635 use_gui = (g_getenv("DISPLAY") != NULL);
636 #else
637 use_gui = TRUE;
638 #endif
639 /* Test whether with/without GUI is forced */
640 for (int i = 1; i < argc; i++) {
641 if (!strcmp(argv[i], "-z")
642 || !strcmp(argv[i], "--without-gui")
643 || !strcmp(argv[i], "-p")
644 || !strncmp(argv[i], "--print", 7)
645 || !strcmp(argv[i], "-e")
646 || !strncmp(argv[i], "--export-png", 12)
647 || !strcmp(argv[i], "-l")
648 || !strncmp(argv[i], "--export-plain-svg", 18)
649 || !strcmp(argv[i], "-i")
650 || !strncmp(argv[i], "--export-area-drawing", 21)
651 || !strcmp(argv[i], "-D")
652 || !strncmp(argv[i], "--export-area-page", 18)
653 || !strcmp(argv[i], "-C")
654 || !strncmp(argv[i], "--export-id", 11)
655 || !strcmp(argv[i], "-P")
656 || !strncmp(argv[i], "--export-ps", 11)
657 || !strcmp(argv[i], "-E")
658 || !strncmp(argv[i], "--export-eps", 12)
659 || !strcmp(argv[i], "-A")
660 || !strncmp(argv[i], "--export-pdf", 12)
661 || !strncmp(argv[i], "--export-latex", 14)
662 #ifdef WIN32
663 || !strcmp(argv[i], "-M")
664 || !strncmp(argv[i], "--export-emf", 12)
665 #endif //WIN32
666 || !strcmp(argv[i], "-W")
667 || !strncmp(argv[i], "--query-width", 13)
668 || !strcmp(argv[i], "-H")
669 || !strncmp(argv[i], "--query-height", 14)
670 || !strcmp(argv[i], "-S")
671 || !strncmp(argv[i], "--query-all", 11)
672 || !strcmp(argv[i], "-X")
673 || !strncmp(argv[i], "--query-x", 9)
674 || !strcmp(argv[i], "-Y")
675 || !strncmp(argv[i], "--query-y", 9)
676 || !strcmp(argv[i], "--vacuum-defs")
677 || !strcmp(argv[i], "--shell")
678 )
679 {
680 /* main_console handles any exports -- not the gui */
681 use_gui = FALSE;
682 break;
683 } else if (!strcmp(argv[i], "-g") || !strcmp(argv[i], "--with-gui")) {
684 use_gui = TRUE;
685 break;
686 }
687 }
689 #ifdef WIN32
690 #ifndef REPLACEARGS_ANSI
691 if ( PrintWin32::is_os_wide() )
692 #endif // REPLACEARGS_ANSI
693 {
694 // If the call fails, we'll need to convert charsets
695 needToRecodeParams = !replaceArgs( argc, argv );
696 }
697 #endif // WIN32
699 /// \todo Should this be a static object (see inkscape.cpp)?
700 Inkscape::NSApplication::Application app(argc, argv, use_gui, sp_new_gui);
702 return app.run();
703 }
708 void fixupSingleFilename( gchar **orig, gchar **spare )
709 {
710 if ( orig && *orig && **orig ) {
711 GError *error = NULL;
712 gchar *newFileName = Inkscape::IO::locale_to_utf8_fallback(*orig, -1, NULL, NULL, &error);
713 if ( newFileName )
714 {
715 *orig = newFileName;
716 if ( spare ) {
717 *spare = newFileName;
718 }
719 // g_message("Set a replacement fixup");
720 }
721 }
722 }
726 GSList *fixupFilenameEncoding( GSList* fl )
727 {
728 GSList *newFl = NULL;
729 while ( fl ) {
730 gchar *fn = static_cast<gchar*>(fl->data);
731 fl = g_slist_remove( fl, fl->data );
732 gchar *newFileName = Inkscape::IO::locale_to_utf8_fallback(fn, -1, NULL, NULL, NULL);
733 if ( newFileName ) {
735 if ( 0 )
736 {
737 gchar *safeFn = Inkscape::IO::sanitizeString(fn);
738 gchar *safeNewFn = Inkscape::IO::sanitizeString(newFileName);
739 GtkWidget *w = gtk_message_dialog_new( NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
740 "Note: Converted '%s' to '%s'", safeFn, safeNewFn );
741 gtk_dialog_run (GTK_DIALOG (w));
742 gtk_widget_destroy (w);
743 g_free(safeNewFn);
744 g_free(safeFn);
745 }
747 g_free( fn );
748 fn = newFileName;
749 newFileName = 0;
750 }
751 else
752 if ( 0 )
753 {
754 gchar *safeFn = Inkscape::IO::sanitizeString(fn);
755 GtkWidget *w = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Error: Unable to convert '%s'", safeFn );
756 gtk_dialog_run (GTK_DIALOG (w));
757 gtk_widget_destroy (w);
758 g_free(safeFn);
759 }
760 newFl = g_slist_append( newFl, fn );
761 }
762 return newFl;
763 }
765 int sp_common_main( int argc, char const **argv, GSList **flDest )
766 {
767 /// \todo fixme: Move these to some centralized location (Lauris)
768 sp_object_type_register("sodipodi:namedview", SP_TYPE_NAMEDVIEW);
769 sp_object_type_register("sodipodi:guide", SP_TYPE_GUIDE);
772 // temporarily switch gettext encoding to locale, so that help messages can be output properly
773 gchar const *charset;
774 g_get_charset(&charset);
776 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
778 poptContext ctx = poptGetContext(NULL, argc, argv, options, 0);
779 poptSetOtherOptionHelp(ctx, _("[OPTIONS...] [FILE...]\n\nAvailable options:"));
780 g_return_val_if_fail(ctx != NULL, 1);
782 /* Collect own arguments */
783 GSList *fl = sp_process_args(ctx);
784 poptFreeContext(ctx);
786 // now switch gettext back to UTF-8 (for GUI)
787 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
789 // Now let's see if the file list still holds up
790 if ( needToRecodeParams )
791 {
792 fl = fixupFilenameEncoding( fl );
793 }
795 // Check the globals for filename-fixup
796 if ( needToRecodeParams )
797 {
798 fixupSingleFilename( &sp_export_png, &sp_export_png_utf8 );
799 fixupSingleFilename( &sp_export_svg, &sp_export_svg_utf8 );
800 fixupSingleFilename( &sp_global_printer, &sp_global_printer_utf8 );
801 }
802 else
803 {
804 if ( sp_export_png )
805 sp_export_png_utf8 = g_strdup( sp_export_png );
806 if ( sp_export_svg )
807 sp_export_svg_utf8 = g_strdup( sp_export_svg );
808 if ( sp_global_printer )
809 sp_global_printer_utf8 = g_strdup( sp_global_printer );
810 }
812 // Return the list if wanted, else free it up.
813 if ( flDest ) {
814 *flDest = fl;
815 fl = 0;
816 } else {
817 while ( fl ) {
818 g_free( fl->data );
819 fl = g_slist_remove( fl, fl->data );
820 }
821 }
822 return 0;
823 }
825 static void
826 snooper(GdkEvent *event, gpointer /*data*/) {
827 if (inkscape_mapalt()) /* returns the map of the keyboard modifier to map to Alt, zero if no mapping */
828 {
829 GdkModifierType mapping=(GdkModifierType)inkscape_mapalt();
830 switch (event->type) {
831 case GDK_MOTION_NOTIFY:
832 if(event->motion.state & mapping) {
833 event->motion.state|=GDK_MOD1_MASK;
834 }
835 break;
836 case GDK_BUTTON_PRESS:
837 if(event->button.state & mapping) {
838 event->button.state|=GDK_MOD1_MASK;
839 }
840 break;
841 case GDK_KEY_PRESS:
842 if(event->key.state & mapping) {
843 event->key.state|=GDK_MOD1_MASK;
844 }
845 break;
846 default:
847 break;
848 }
849 }
851 if (inkscape_trackalt()) {
852 // MacOS X with X11 has some problem with the default
853 // xmodmapping. A ~/.xmodmap solution does not work reliably due
854 // to the way we package our executable in a .app that can launch
855 // X11 or use an already-running X11. The same problem has been
856 // reported on Linux but there is no .app/X11 to get in the way
857 // of ~/.xmodmap fixes. So we make this a preference.
858 //
859 // For some reason, Gdk senses changes in Alt (Mod1) state for
860 // many message types, but not for keystrokes! So this ugly hack
861 // tracks what the state of Alt-pressing is, and ensures
862 // GDK_MOD1_MASK is in the event->key.state as appropriate.
863 //
864 static gboolean altL_pressed = FALSE;
865 static gboolean altR_pressed = FALSE;
866 static gboolean alt_pressed = FALSE;
867 guint get_group0_keyval(GdkEventKey* event);
868 guint keyval = 0;
869 switch (event->type) {
870 case GDK_MOTION_NOTIFY:
871 alt_pressed = TRUE && (event->motion.state & GDK_MOD1_MASK);
872 break;
873 case GDK_BUTTON_PRESS:
874 alt_pressed = TRUE && (event->button.state & GDK_MOD1_MASK);
875 break;
876 case GDK_KEY_PRESS:
877 keyval = get_group0_keyval(&event->key);
878 if (keyval == GDK_Alt_L) altL_pressed = TRUE;
879 if (keyval == GDK_Alt_R) altR_pressed = TRUE;
880 alt_pressed = alt_pressed || altL_pressed || altR_pressed;
881 alt_pressed = alt_pressed || (event->button.state & GDK_MOD1_MASK);
882 if (alt_pressed)
883 event->key.state |= GDK_MOD1_MASK;
884 else
885 event->key.state &= ~GDK_MOD1_MASK;
886 break;
887 case GDK_KEY_RELEASE:
888 keyval = get_group0_keyval(&event->key);
889 if (keyval == GDK_Alt_L) altL_pressed = FALSE;
890 if (keyval == GDK_Alt_R) altR_pressed = FALSE;
891 if (!altL_pressed && !altR_pressed)
892 alt_pressed = FALSE;
893 break;
894 default:
895 break;
896 }
897 //printf("alt_pressed: %s\n", alt_pressed? "+" : "-");
898 }
900 gtk_main_do_event (event);
901 }
903 static std::vector<Glib::ustring> getDirectorySet(const gchar* userDir, const gchar* const * systemDirs) {
904 std::vector<Glib::ustring> listing;
905 listing.push_back(userDir);
906 for ( const char* const* cur = systemDirs; *cur; cur++ )
907 {
908 listing.push_back(*cur);
909 }
910 return listing;
911 }
913 int
914 sp_main_gui(int argc, char const **argv)
915 {
916 Gtk::Main main_instance (&argc, const_cast<char ***>(&argv));
918 GSList *fl = NULL;
919 int retVal = sp_common_main( argc, argv, &fl );
920 g_return_val_if_fail(retVal == 0, 1);
922 // Add possible icon entry directories
923 std::vector<Glib::ustring> dataDirs = getDirectorySet( g_get_user_data_dir(),
924 g_get_system_data_dirs() );
925 for (std::vector<Glib::ustring>::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it)
926 {
927 std::vector<Glib::ustring> listing;
928 listing.push_back(*it);
929 listing.push_back("inkscape");
930 listing.push_back("icons");
931 Glib::ustring dir = Glib::build_filename(listing);
932 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), dir.c_str());
933 }
935 // Add our icon directory to the search path for icon theme lookups.
936 gchar *usericondir = profile_path("icons");
937 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), usericondir);
938 gtk_icon_theme_append_search_path(gtk_icon_theme_get_default(), INKSCAPE_PIXMAPDIR);
939 g_free(usericondir);
941 gdk_event_handler_set((GdkEventFunc)snooper, NULL, NULL);
942 Inkscape::Debug::log_display_config();
944 // Set default window icon. Obeys the theme.
945 gtk_window_set_default_icon_name("inkscape");
946 // Do things that were previously in inkscape_gtk_stock_init().
947 sp_icon_get_phys_size(GTK_ICON_SIZE_MENU);
948 Inkscape::UI::Widget::Panel::prep();
950 gboolean create_new = TRUE;
952 /// \todo FIXME BROKEN - non-UTF-8 sneaks in here.
953 inkscape_application_init(argv[0], true);
955 while (fl) {
956 if (sp_file_open((gchar *)fl->data,NULL)) {
957 create_new=FALSE;
958 }
959 fl = g_slist_remove(fl, fl->data);
960 }
961 if (create_new) {
962 sp_file_new_default();
963 }
965 Glib::signal_idle().connect(sigc::ptr_fun(&Inkscape::CmdLineAction::idle));
966 main_instance.run();
968 #ifdef WIN32
969 //We might not need anything here
970 //sp_win32_finish(); <-- this is a NOP func
971 #endif
973 return 0;
974 }
976 /**
977 * Process file list
978 */
979 void sp_process_file_list(GSList *fl)
980 {
981 while (fl) {
982 const gchar *filename = (gchar *)fl->data;
984 SPDocument *doc = NULL;
985 try {
986 doc = Inkscape::Extension::open(NULL, filename);
987 } catch (Inkscape::Extension::Input::no_extension_found &e) {
988 doc = NULL;
989 } catch (Inkscape::Extension::Input::open_failed &e) {
990 doc = NULL;
991 }
993 if (doc == NULL) {
994 try {
995 doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), filename);
996 } catch (Inkscape::Extension::Input::no_extension_found &e) {
997 doc = NULL;
998 } catch (Inkscape::Extension::Input::open_failed &e) {
999 doc = NULL;
1000 }
1001 }
1002 if (doc == NULL) {
1003 g_warning("Specified document %s cannot be opened (does not exist or not a valid SVG file)", filename);
1004 } else {
1005 if (sp_vacuum_defs) {
1006 vacuum_document(doc);
1007 }
1008 if (sp_vacuum_defs && !sp_export_svg) {
1009 // save under the name given in the command line
1010 sp_repr_save_file(doc->rdoc, filename, SP_SVG_NS_URI);
1011 }
1012 if (sp_global_printer) {
1013 sp_print_document_to_file(doc, sp_global_printer);
1014 }
1015 if (sp_export_png || (sp_export_id && sp_export_use_hints)) {
1016 sp_do_export_png(doc);
1017 }
1018 if (sp_export_svg) {
1019 Inkscape::XML::Document *rdoc;
1020 Inkscape::XML::Node *repr;
1021 rdoc = sp_repr_document_new("svg:svg");
1022 repr = rdoc->root();
1023 repr = sp_document_root(doc)->updateRepr(rdoc, repr, SP_OBJECT_WRITE_BUILD);
1024 sp_repr_save_rebased_file(repr->document(), sp_export_svg, SP_SVG_NS_URI,
1025 doc->base, sp_export_svg);
1026 }
1027 if (sp_export_ps) {
1028 do_export_ps_pdf(doc, sp_export_ps, "image/x-postscript");
1029 }
1030 if (sp_export_eps) {
1031 do_export_ps_pdf(doc, sp_export_eps, "image/x-e-postscript");
1032 }
1033 if (sp_export_pdf) {
1034 do_export_ps_pdf(doc, sp_export_pdf, "application/pdf");
1035 }
1036 #ifdef WIN32
1037 if (sp_export_emf) {
1038 do_export_emf(doc, sp_export_emf, "image/x-emf");
1039 }
1040 #endif //WIN32
1041 if (sp_query_all) {
1042 do_query_all (doc);
1043 } else if (sp_query_width || sp_query_height) {
1044 do_query_dimension (doc, true, sp_query_width? Geom::X : Geom::Y, sp_query_id);
1045 } else if (sp_query_x || sp_query_y) {
1046 do_query_dimension (doc, false, sp_query_x? Geom::X : Geom::Y, sp_query_id);
1047 }
1049 delete doc;
1050 }
1051 fl = g_slist_remove(fl, fl->data);
1052 }
1053 }
1055 /**
1056 * Run the application as an interactive shell, parsing command lines from stdin
1057 * Returns -1 on error.
1058 */
1059 int sp_main_shell(char const* command_name)
1060 {
1061 int retval = 0;
1063 const unsigned int buffer_size = 4096;
1064 gchar *command_line = g_strnfill(buffer_size, 0);
1065 g_strlcpy(command_line, command_name, buffer_size);
1066 gsize offset = g_strlcat(command_line, " ", buffer_size);
1067 gsize sizeLeft = buffer_size - offset;
1068 gchar *useme = command_line + offset;
1070 fprintf(stdout, "Inkscape %s interactive shell mode. Type 'quit' to quit.\n", Inkscape::version_string);
1071 fflush(stdout);
1072 char* linedata = 0;
1073 do {
1074 fprintf(stdout, ">");
1075 fflush(stdout);
1076 if ((linedata = fgets(useme, sizeLeft, stdin))) {
1077 size_t len = strlen(useme);
1078 if ( (len >= sizeLeft - 1) || (useme[len - 1] != '\n') ) {
1079 fprintf(stdout, "ERROR: Command line too long\n");
1080 // Consume rest of line
1081 retval = -1; // If the while loop completes, this remains -1
1082 while (fgets(useme, sizeLeft, stdin) && retval) {
1083 len = strlen(command_line);
1084 if ( (len < buffer_size) && (command_line[len-1] == '\n') ) {
1085 retval = 0;
1086 }
1087 }
1088 } else {
1089 useme[--len] = '\0'; // Strip newline
1090 if (useme[len - 1] == '\r') {
1091 useme[--len] = '\0';
1092 }
1093 if ( strcmp(useme, "quit") == 0 ) {
1094 // Time to quit
1095 fflush(stdout);
1096 linedata = 0; // mark for exit
1097 } else if ( len < 1 ) {
1098 // blank string. Do nothing.
1099 } else {
1100 GError* parseError = 0;
1101 gchar** argv = 0;
1102 gint argc = 0;
1103 if ( g_shell_parse_argv(command_line, &argc, &argv, &parseError) ) {
1104 poptContext ctx = poptGetContext(NULL, argc, const_cast<const gchar**>(argv), options, 0);
1105 poptSetOtherOptionHelp(ctx, _("[OPTIONS...] [FILE...]\n\nAvailable options:"));
1106 if ( ctx ) {
1107 GSList *fl = sp_process_args(ctx);
1108 sp_process_file_list(fl);
1109 poptFreeContext(ctx);
1110 } else {
1111 retval = 1; // not sure why. But this was the previous return value
1112 }
1113 resetCommandlineGlobals();
1114 g_strfreev(argv);
1115 } else {
1116 g_warning("Cannot parse commandline: %s", useme);
1117 }
1118 }
1119 }
1120 } // if (linedata...
1121 } while (linedata && (retval == 0));
1123 g_free(command_line);
1124 return retval;
1125 }
1127 int sp_main_console(int argc, char const **argv)
1128 {
1129 /* We are started in text mode */
1131 /* Do this g_type_init(), so that we can use Xft/Freetype2 (Pango)
1132 * in a non-Gtk environment. Used in libnrtype's
1133 * FontInstance.cpp and FontFactory.cpp.
1134 * http://mail.gnome.org/archives/gtk-list/2003-December/msg00063.html
1135 */
1136 g_type_init();
1137 char **argv2 = const_cast<char **>(argv);
1138 gtk_init_check( &argc, &argv2 );
1139 //setlocale(LC_ALL, "");
1141 GSList *fl = NULL;
1142 int retVal = sp_common_main( argc, argv, &fl );
1143 g_return_val_if_fail(retVal == 0, 1);
1145 if (fl == NULL && !sp_shell) {
1146 g_print("Nothing to do!\n");
1147 exit(0);
1148 }
1150 inkscape_application_init(argv[0], false);
1152 if (sp_shell) {
1153 sp_main_shell(argv[0]); // Run as interactive shell
1154 exit(0);
1155 } else {
1156 sp_process_file_list(fl); // Normal command line invokation
1157 }
1159 return 0;
1160 }
1162 static void
1163 do_query_dimension (SPDocument *doc, bool extent, Geom::Dim2 const axis, const gchar *id)
1164 {
1165 SPObject *o = NULL;
1167 if (id) {
1168 o = doc->getObjectById(id);
1169 if (o) {
1170 if (!SP_IS_ITEM (o)) {
1171 g_warning("Object with id=\"%s\" is not a visible item. Cannot query dimensions.", id);
1172 return;
1173 }
1174 } else {
1175 g_warning("Object with id=\"%s\" is not found. Cannot query dimensions.", id);
1176 return;
1177 }
1178 } else {
1179 o = SP_DOCUMENT_ROOT(doc);
1180 }
1182 if (o) {
1183 sp_document_ensure_up_to_date (doc);
1184 SPItem *item = ((SPItem *) o);
1186 // "true" SVG bbox for scripting
1187 Geom::OptRect area = item->getBounds(sp_item_i2doc_affine(item));
1188 if (area) {
1189 Inkscape::SVGOStringStream os;
1190 if (extent) {
1191 os << area->dimensions()[axis];
1192 } else {
1193 os << area->min()[axis];
1194 }
1195 g_print ("%s", os.str().c_str());
1196 } else {
1197 g_print("0");
1198 }
1199 }
1200 }
1202 static void
1203 do_query_all (SPDocument *doc)
1204 {
1205 SPObject *o = NULL;
1207 o = SP_DOCUMENT_ROOT(doc);
1209 if (o) {
1210 sp_document_ensure_up_to_date (doc);
1211 do_query_all_recurse(o);
1212 }
1213 }
1215 static void
1216 do_query_all_recurse (SPObject *o)
1217 {
1218 SPItem *item = ((SPItem *) o);
1219 if (o->getId() && SP_IS_ITEM(item)) {
1220 Geom::OptRect area = item->getBounds(sp_item_i2doc_affine(item));
1221 if (area) {
1222 Inkscape::SVGOStringStream os;
1223 os << o->getId();
1224 os << "," << area->min()[Geom::X];
1225 os << "," << area->min()[Geom::Y];
1226 os << "," << area->dimensions()[Geom::X];
1227 os << "," << area->dimensions()[Geom::Y];
1228 g_print ("%s\n", os.str().c_str());
1229 }
1230 }
1232 SPObject *child = o->children;
1233 while (child) {
1234 do_query_all_recurse (child);
1235 child = child->next;
1236 }
1237 }
1240 static void
1241 sp_do_export_png(SPDocument *doc)
1242 {
1243 const gchar *filename = NULL;
1244 bool filename_from_hint = false;
1245 gdouble dpi = 0.0;
1247 if (sp_export_use_hints && (!sp_export_id && !sp_export_area_drawing)) {
1248 g_warning ("--export-use-hints can only be used with --export-id or --export-area-drawing; ignored.");
1249 }
1251 GSList *items = NULL;
1253 Geom::Rect area;
1254 if (sp_export_id || sp_export_area_drawing) {
1256 SPObject *o = NULL;
1257 SPObject *o_area = NULL;
1258 if (sp_export_id && sp_export_area_drawing) {
1259 o = doc->getObjectById(sp_export_id);
1260 o_area = SP_DOCUMENT_ROOT (doc);
1261 } else if (sp_export_id) {
1262 o = doc->getObjectById(sp_export_id);
1263 o_area = o;
1264 } else if (sp_export_area_drawing) {
1265 o = SP_DOCUMENT_ROOT (doc);
1266 o_area = o;
1267 }
1269 if (o) {
1270 if (!SP_IS_ITEM (o)) {
1271 g_warning("Object with id=\"%s\" is not a visible item. Nothing exported.", sp_export_id);
1272 return;
1273 }
1275 items = g_slist_prepend (items, SP_ITEM(o));
1277 if (sp_export_id_only) {
1278 g_print("Exporting only object with id=\"%s\"; all other objects hidden\n", sp_export_id);
1279 }
1281 if (sp_export_use_hints) {
1283 // retrieve export filename hint
1284 const gchar *fn_hint = SP_OBJECT_REPR(o)->attribute("inkscape:export-filename");
1285 if (fn_hint) {
1286 if (sp_export_png) {
1287 g_warning ("Using export filename from the command line (--export-png). Filename hint %s is ignored.", fn_hint);
1288 filename = sp_export_png;
1289 } else {
1290 filename = fn_hint;
1291 filename_from_hint = true;
1292 }
1293 } else {
1294 g_warning ("Export filename hint not found for the object.");
1295 filename = sp_export_png;
1296 }
1298 // retrieve export dpi hints
1299 const gchar *dpi_hint = SP_OBJECT_REPR(o)->attribute("inkscape:export-xdpi"); // only xdpi, ydpi is always the same now
1300 if (dpi_hint) {
1301 if (sp_export_dpi || sp_export_width || sp_export_height) {
1302 g_warning ("Using bitmap dimensions from the command line (--export-dpi, --export-width, or --export-height). DPI hint %s is ignored.", dpi_hint);
1303 } else {
1304 dpi = atof(dpi_hint);
1305 }
1306 } else {
1307 g_warning ("Export DPI hint not found for the object.");
1308 }
1310 }
1312 // write object bbox to area
1313 sp_document_ensure_up_to_date (doc);
1314 Geom::OptRect areaMaybe;
1315 sp_item_invoke_bbox((SPItem *) o_area, areaMaybe, sp_item_i2d_affine((SPItem *) o_area), TRUE);
1316 if (areaMaybe) {
1317 area = *areaMaybe;
1318 } else {
1319 g_warning("Unable to determine a valid bounding box. Nothing exported.");
1320 return;
1321 }
1322 } else {
1323 g_warning("Object with id=\"%s\" was not found in the document. Nothing exported.", sp_export_id);
1324 return;
1325 }
1326 }
1328 if (sp_export_area) {
1329 /* Try to parse area (given in SVG pixels) */
1330 gdouble x0,y0,x1,y1;
1331 if (!sscanf(sp_export_area, "%lg:%lg:%lg:%lg", &x0, &y0, &x1, &y1) == 4) {
1332 g_warning("Cannot parse export area '%s'; use 'x0:y0:x1:y1'. Nothing exported.", sp_export_area);
1333 return;
1334 }
1335 area = Geom::Rect(Geom::Interval(x0,x1), Geom::Interval(y0,y1));
1336 } else if (sp_export_area_page || !(sp_export_id || sp_export_area_drawing)) {
1337 /* Export the whole page: note: Inkscape uses 'page' in all menus and dialogs, not 'canvas' */
1338 sp_document_ensure_up_to_date (doc);
1339 Geom::Point origin (SP_ROOT(doc->root)->x.computed, SP_ROOT(doc->root)->y.computed);
1340 area = Geom::Rect(origin, origin + sp_document_dimensions(doc));
1341 }
1343 // set filename and dpi from options, if not yet set from the hints
1344 if (!filename) {
1345 if (!sp_export_png) {
1346 g_warning ("No export filename given and no filename hint. Nothing exported.");
1347 return;
1348 }
1349 filename = sp_export_png;
1350 }
1352 if (sp_export_dpi && dpi == 0.0) {
1353 dpi = atof(sp_export_dpi);
1354 if ((dpi < 0.1) || (dpi > 10000.0)) {
1355 g_warning("DPI value %s out of range [0.1 - 10000.0]. Nothing exported.", sp_export_dpi);
1356 return;
1357 }
1358 g_print("DPI: %g\n", dpi);
1359 }
1361 if (sp_export_area_snap) {
1362 round_rectangle_outwards(area);
1363 }
1365 // default dpi
1366 if (dpi == 0.0) {
1367 dpi = PX_PER_IN;
1368 }
1370 unsigned long int width = 0;
1371 unsigned long int height = 0;
1373 if (sp_export_width) {
1374 errno=0;
1375 width = strtoul(sp_export_width, NULL, 0);
1376 if ((width < 1) || (width > PNG_UINT_31_MAX) || (errno == ERANGE) ) {
1377 g_warning("Export width %lu out of range (1 - %lu). Nothing exported.", width, (unsigned long int)PNG_UINT_31_MAX);
1378 return;
1379 }
1380 dpi = (gdouble) width * PX_PER_IN / area.width();
1381 }
1383 if (sp_export_height) {
1384 errno=0;
1385 height = strtoul(sp_export_height, NULL, 0);
1386 if ((height < 1) || (height > PNG_UINT_31_MAX)) {
1387 g_warning("Export height %lu out of range (1 - %lu). Nothing exported.", height, (unsigned long int)PNG_UINT_31_MAX);
1388 return;
1389 }
1390 dpi = (gdouble) height * PX_PER_IN / area.height();
1391 }
1393 if (!sp_export_width) {
1394 width = (unsigned long int) (area.width() * dpi / PX_PER_IN + 0.5);
1395 }
1397 if (!sp_export_height) {
1398 height = (unsigned long int) (area.height() * dpi / PX_PER_IN + 0.5);
1399 }
1401 guint32 bgcolor = 0x00000000;
1402 if (sp_export_background) {
1403 // override the page color
1404 bgcolor = sp_svg_read_color(sp_export_background, 0xffffff00);
1405 bgcolor |= 0xff; // default is no opacity
1406 } else {
1407 // read from namedview
1408 Inkscape::XML::Node *nv = sp_repr_lookup_name (doc->rroot, "sodipodi:namedview");
1409 if (nv && nv->attribute("pagecolor"))
1410 bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
1411 if (nv && nv->attribute("inkscape:pageopacity"))
1412 bgcolor |= SP_COLOR_F_TO_U(sp_repr_get_double_attribute (nv, "inkscape:pageopacity", 1.0));
1413 }
1415 if (sp_export_background_opacity) {
1416 // override opacity
1417 gfloat value;
1418 if (sp_svg_number_read_f (sp_export_background_opacity, &value)) {
1419 if (value > 1.0) {
1420 value = CLAMP (value, 1.0f, 255.0f);
1421 bgcolor &= (guint32) 0xffffff00;
1422 bgcolor |= (guint32) floor(value);
1423 } else {
1424 value = CLAMP (value, 0.0f, 1.0f);
1425 bgcolor &= (guint32) 0xffffff00;
1426 bgcolor |= SP_COLOR_F_TO_U(value);
1427 }
1428 }
1429 }
1431 gchar *path = 0;
1432 if (filename_from_hint) {
1433 //Make relative paths go from the document location, if possible:
1434 if (!g_path_is_absolute(filename) && doc->uri) {
1435 gchar *dirname = g_path_get_dirname(doc->uri);
1436 if (dirname) {
1437 path = g_build_filename(dirname, filename, NULL);
1438 g_free(dirname);
1439 }
1440 }
1441 if (!path) {
1442 path = g_strdup(filename);
1443 }
1444 } else {
1445 path = g_strdup(filename);
1446 }
1448 g_print("Background RRGGBBAA: %08x\n", bgcolor);
1450 g_print("Area %g:%g:%g:%g exported to %lu x %lu pixels (%g dpi)\n", area[Geom::X][0], area[Geom::Y][0], area[Geom::X][1], area[Geom::Y][1], width, height, dpi);
1452 g_print("Bitmap saved as: %s\n", filename);
1454 if ((width >= 1) && (height >= 1) && (width <= PNG_UINT_31_MAX) && (height <= PNG_UINT_31_MAX)) {
1455 sp_export_png_file(doc, path, area, width, height, dpi, dpi, bgcolor, NULL, NULL, true, sp_export_id_only ? items : NULL);
1456 } else {
1457 g_warning("Calculated bitmap dimensions %lu %lu are out of range (1 - %lu). Nothing exported.", width, height, (unsigned long int)PNG_UINT_31_MAX);
1458 }
1460 g_free (path);
1461 g_slist_free (items);
1462 }
1465 /**
1466 * Perform a PDF/PS/EPS export
1467 *
1468 * \param doc Document to export.
1469 * \param uri URI to export to.
1470 * \param mime MIME type to export as.
1471 */
1473 static void do_export_ps_pdf(SPDocument* doc, gchar const* uri, char const* mime)
1474 {
1475 Inkscape::Extension::DB::OutputList o;
1476 Inkscape::Extension::db.get_output_list(o);
1477 Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
1478 while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1479 i++;
1480 }
1482 if (i == o.end())
1483 {
1484 g_warning ("Could not find an extension to export to MIME type %s.", mime);
1485 return;
1486 }
1488 if (sp_export_id) {
1489 SPObject *o = doc->getObjectById(sp_export_id);
1490 if (o == NULL) {
1491 g_warning("Object with id=\"%s\" was not found in the document. Nothing exported.", sp_export_id);
1492 return;
1493 }
1494 (*i)->set_param_string ("exportId", sp_export_id);
1495 } else {
1496 (*i)->set_param_string ("exportId", "");
1497 }
1499 if (sp_export_area_page && sp_export_area_drawing) {
1500 g_warning ("You cannot use --export-area-page and --export-area-drawing at the same time; only the former will take effect.");
1501 sp_export_area_drawing = false;
1502 }
1504 if (sp_export_area_drawing) {
1505 (*i)->set_param_bool ("areaDrawing", TRUE);
1506 } else {
1507 (*i)->set_param_bool ("areaDrawing", FALSE);
1508 }
1510 if (sp_export_area_page) {
1511 if (sp_export_eps) {
1512 g_warning ("EPS cannot have its bounding box extend beyond its content, so if your drawing is smaller than the page, --export-area-page will clip it to drawing.");
1513 }
1514 (*i)->set_param_bool ("areaPage", TRUE);
1515 } else {
1516 (*i)->set_param_bool ("areaPage", FALSE);
1517 }
1519 if (!sp_export_area_drawing && !sp_export_area_page && !sp_export_id) {
1520 // neither is set, set page as default for ps/pdf and drawing for eps
1521 if (sp_export_eps) {
1522 try {
1523 (*i)->set_param_bool("areaDrawing", TRUE);
1524 } catch (...) {}
1525 }
1526 }
1528 if (sp_export_text_to_path) {
1529 (*i)->set_param_bool("textToPath", TRUE);
1530 } else {
1531 (*i)->set_param_bool("textToPath", FALSE);
1532 }
1534 if (sp_export_latex) {
1535 (*i)->set_param_bool("textToLaTeX", TRUE);
1536 } else {
1537 (*i)->set_param_bool("textToLaTeX", FALSE);
1538 }
1540 if (sp_export_ignore_filters) {
1541 (*i)->set_param_bool("blurToBitmap", FALSE);
1542 } else {
1543 (*i)->set_param_bool("blurToBitmap", TRUE);
1545 gdouble dpi = 90.0;
1546 if (sp_export_dpi) {
1547 dpi = atof(sp_export_dpi);
1548 if ((dpi < 1) || (dpi > 10000.0)) {
1549 g_warning("DPI value %s out of range [1 - 10000]. Using 90 dpi instead.", sp_export_dpi);
1550 dpi = 90;
1551 }
1552 }
1554 (*i)->set_param_int("resolution", (int) dpi);
1555 }
1557 (*i)->save(doc, uri);
1558 }
1560 #ifdef WIN32
1561 /**
1562 * Export a document to EMF
1563 *
1564 * \param doc Document to export.
1565 * \param uri URI to export to.
1566 * \param mime MIME type to export as (should be "image/x-emf")
1567 */
1569 static void do_export_emf(SPDocument* doc, gchar const* uri, char const* mime)
1570 {
1571 Inkscape::Extension::DB::OutputList o;
1572 Inkscape::Extension::db.get_output_list(o);
1573 Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
1574 while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1575 i++;
1576 }
1578 if (i == o.end())
1579 {
1580 g_warning ("Could not find an extension to export to MIME type %s.", mime);
1581 return;
1582 }
1584 (*i)->save(doc, uri);
1585 }
1586 #endif //WIN32
1588 #ifdef WIN32
1589 bool replaceArgs( int& argc, char**& argv )
1590 {
1591 bool worked = false;
1593 #ifdef REPLACEARGS_DEBUG
1594 MessageBoxA( NULL, "GetCommandLineW() getting called", "GetCommandLineW", MB_OK | MB_ICONINFORMATION );
1595 #endif // REPLACEARGS_DEBUG
1597 wchar_t* line = GetCommandLineW();
1598 if ( line )
1599 {
1600 #ifdef REPLACEARGS_DEBUG
1601 {
1602 gchar* utf8Line = g_utf16_to_utf8( (gunichar2*)line, -1, NULL, NULL, NULL );
1603 if ( utf8Line )
1604 {
1605 gchar *safe = Inkscape::IO::sanitizeString(utf8Line);
1606 {
1607 char tmp[strlen(safe) + 32];
1608 snprintf( tmp, sizeof(tmp), "GetCommandLineW() = '%s'", safe );
1609 MessageBoxA( NULL, tmp, "GetCommandLineW", MB_OK | MB_ICONINFORMATION );
1610 }
1611 }
1612 }
1613 #endif // REPLACEARGS_DEBUG
1615 int numArgs = 0;
1616 wchar_t** parsed = CommandLineToArgvW( line, &numArgs );
1618 #ifdef REPLACEARGS_ANSI
1619 // test code for trying things on Win95/98/ME
1620 if ( !parsed )
1621 {
1622 #ifdef REPLACEARGS_DEBUG
1623 MessageBoxA( NULL, "Unable to process command-line. Faking it", "CommandLineToArgvW", MB_OK | MB_ICONINFORMATION );
1624 #endif // REPLACEARGS_DEBUG
1625 int lineLen = wcslen(line) + 1;
1626 wchar_t* lineDup = new wchar_t[lineLen];
1627 wcsncpy( lineDup, line, lineLen );
1629 int pos = 0;
1630 bool inQuotes = false;
1631 bool inWhitespace = true;
1632 std::vector<int> places;
1633 while ( lineDup[pos] )
1634 {
1635 if ( inQuotes )
1636 {
1637 if ( lineDup[pos] == L'"' )
1638 {
1639 inQuotes = false;
1640 }
1641 }
1642 else if ( lineDup[pos] == L'"' )
1643 {
1644 inQuotes = true;
1645 inWhitespace = false;
1646 places.push_back(pos);
1647 }
1648 else if ( lineDup[pos] == L' ' || lineDup[pos] == L'\t' )
1649 {
1650 if ( !inWhitespace )
1651 {
1652 inWhitespace = true;
1653 lineDup[pos] = 0;
1654 }
1655 }
1656 else if ( inWhitespace && (lineDup[pos] != L' ' && lineDup[pos] != L'\t') )
1657 {
1658 inWhitespace = false;
1659 places.push_back(pos);
1660 }
1661 else
1662 {
1663 // consume
1664 }
1665 pos++;
1666 }
1667 #ifdef REPLACEARGS_DEBUG
1668 {
1669 char tmp[256];
1670 snprintf( tmp, sizeof(tmp), "Counted %d args", places.size() );
1671 MessageBoxA( NULL, tmp, "CommandLineToArgvW", MB_OK | MB_ICONINFORMATION );
1672 }
1673 #endif // REPLACEARGS_DEBUG
1675 wchar_t** block = new wchar_t*[places.size()];
1676 int i = 0;
1677 for ( std::vector<int>::iterator it = places.begin(); it != places.end(); it++ )
1678 {
1679 block[i++] = &lineDup[*it];
1680 }
1681 parsed = block;
1682 numArgs = places.size();
1683 }
1684 #endif // REPLACEARGS_ANSI
1686 if ( parsed )
1687 {
1688 std::vector<wchar_t*>expandedArgs;
1689 if ( numArgs > 0 )
1690 {
1691 expandedArgs.push_back( parsed[0] );
1692 }
1694 for ( int i1 = 1; i1 < numArgs; i1++ )
1695 {
1696 bool wildcarded = (wcschr(parsed[i1], L'?') != NULL) || (wcschr(parsed[i1], L'*') != NULL);
1697 wildcarded &= parsed[i1][0] != L'"';
1698 wildcarded &= parsed[i1][0] != L'-';
1699 if ( wildcarded )
1700 {
1701 #ifdef REPLACEARGS_ANSI
1702 WIN32_FIND_DATAA data;
1703 #else
1704 WIN32_FIND_DATAW data;
1705 #endif // REPLACEARGS_ANSI
1707 memset((void *)&data, 0, sizeof(data));
1709 int baseLen = wcslen(parsed[i1]) + 2;
1710 wchar_t* base = new wchar_t[baseLen];
1711 wcsncpy( base, parsed[i1], baseLen );
1712 wchar_t* last = wcsrchr( base, L'\\' );
1713 if ( last )
1714 {
1715 last[1] = 0;
1716 }
1717 else
1718 {
1719 base[0] = 0;
1720 }
1721 baseLen = wcslen( base );
1723 #ifdef REPLACEARGS_ANSI
1724 char target[MAX_PATH];
1725 if ( WideCharToMultiByte( CP_ACP, 0, parsed[i1], -1, target, sizeof(target), NULL, NULL) )
1726 {
1727 HANDLE hf = FindFirstFileA( target, &data );
1728 #else
1729 HANDLE hf = FindFirstFileW( parsed[i1], &data );
1730 #endif // REPLACEARGS_ANSI
1731 if ( hf != INVALID_HANDLE_VALUE )
1732 {
1733 BOOL found = TRUE;
1734 do
1735 {
1736 #ifdef REPLACEARGS_ANSI
1737 int howMany = MultiByteToWideChar( CP_ACP, 0, data.cFileName, -1, NULL, 0 );
1738 if ( howMany > 0 )
1739 {
1740 howMany += baseLen;
1741 wchar_t* tmp = new wchar_t[howMany + 1];
1742 wcsncpy( tmp, base, howMany + 1 );
1743 MultiByteToWideChar( CP_ACP, 0, data.cFileName, -1, tmp + baseLen, howMany + 1 - baseLen );
1744 expandedArgs.push_back( tmp );
1745 found = FindNextFileA( hf, &data );
1746 }
1747 #else
1748 int howMany = wcslen(data.cFileName) + baseLen;
1749 wchar_t* tmp = new wchar_t[howMany + 1];
1750 wcsncpy( tmp, base, howMany + 1 );
1751 wcsncat( tmp, data.cFileName, howMany + 1 );
1752 expandedArgs.push_back( tmp );
1753 found = FindNextFileW( hf, &data );
1754 #endif // REPLACEARGS_ANSI
1755 } while ( found );
1757 FindClose( hf );
1758 }
1759 else
1760 {
1761 expandedArgs.push_back( parsed[i1] );
1762 }
1763 #ifdef REPLACEARGS_ANSI
1764 }
1765 #endif // REPLACEARGS_ANSI
1767 delete[] base;
1768 }
1769 else
1770 {
1771 expandedArgs.push_back( parsed[i1] );
1772 }
1773 }
1775 {
1776 wchar_t** block = new wchar_t*[expandedArgs.size()];
1777 int iz = 0;
1778 for ( std::vector<wchar_t*>::iterator it = expandedArgs.begin(); it != expandedArgs.end(); it++ )
1779 {
1780 block[iz++] = *it;
1781 }
1782 parsed = block;
1783 numArgs = expandedArgs.size();
1784 }
1786 std::vector<gchar*> newArgs;
1787 for ( int i = 0; i < numArgs; i++ )
1788 {
1789 gchar* replacement = g_utf16_to_utf8( (gunichar2*)parsed[i], -1, NULL, NULL, NULL );
1790 if ( replacement )
1791 {
1792 #ifdef REPLACEARGS_DEBUG
1793 gchar *safe2 = Inkscape::IO::sanitizeString(replacement);
1795 if ( safe2 )
1796 {
1797 {
1798 char tmp[1024];
1799 snprintf( tmp, sizeof(tmp), " [%2d] = '%s'", i, safe2 );
1800 MessageBoxA( NULL, tmp, "GetCommandLineW", MB_OK | MB_ICONINFORMATION );
1801 }
1802 g_free( safe2 );
1803 }
1804 #endif // REPLACEARGS_DEBUG
1806 newArgs.push_back( replacement );
1807 }
1808 else
1809 {
1810 newArgs.push_back( blankParam );
1811 }
1812 }
1814 // Now push our munged params to be the new argv and argc
1815 {
1816 char** block = new char*[newArgs.size()];
1817 int iz = 0;
1818 for ( std::vector<char*>::iterator it = newArgs.begin(); it != newArgs.end(); it++ )
1819 {
1820 block[iz++] = *it;
1821 }
1822 argv = block;
1823 argc = newArgs.size();
1824 worked = true;
1825 }
1826 }
1827 #ifdef REPLACEARGS_DEBUG
1828 else
1829 {
1830 MessageBoxA( NULL, "Unable to process command-line", "CommandLineToArgvW", MB_OK | MB_ICONINFORMATION );
1831 }
1832 #endif // REPLACEARGS_DEBUG
1833 }
1834 #ifdef REPLACEARGS_DEBUG
1835 else
1836 {
1837 {
1838 MessageBoxA( NULL, "Unable to fetch result from GetCommandLineW()", "GetCommandLineW", MB_OK | MB_ICONINFORMATION );
1839 }
1841 char* line2 = GetCommandLineA();
1842 if ( line2 )
1843 {
1844 gchar *safe = Inkscape::IO::sanitizeString(line2);
1845 {
1846 {
1847 char tmp[strlen(safe) + 32];
1848 snprintf( tmp, sizeof(tmp), "GetCommandLineA() = '%s'", safe );
1849 MessageBoxA( NULL, tmp, "GetCommandLineA", MB_OK | MB_ICONINFORMATION );
1850 }
1851 }
1852 }
1853 else
1854 {
1855 MessageBoxA( NULL, "Unable to fetch result from GetCommandLineA()", "GetCommandLineA", MB_OK | MB_ICONINFORMATION );
1856 }
1857 }
1858 #endif // REPLACEARGS_DEBUG
1860 return worked;
1861 }
1862 #endif // WIN32
1864 static GSList *
1865 sp_process_args(poptContext ctx)
1866 {
1867 GSList *fl = NULL;
1869 gint a;
1870 while ((a = poptGetNextOpt(ctx)) != -1) {
1871 switch (a) {
1872 case SP_ARG_FILE: {
1873 gchar const *fn = poptGetOptArg(ctx);
1874 if (fn != NULL) {
1875 fl = g_slist_append(fl, g_strdup(fn));
1876 }
1877 break;
1878 }
1879 case SP_ARG_VERSION: {
1880 printf("Inkscape %s (%s)\n", Inkscape::version_string, __DATE__);
1881 exit(0);
1882 break;
1883 }
1884 case SP_ARG_EXTENSIONDIR: {
1885 printf("%s\n", INKSCAPE_EXTENSIONDIR);
1886 exit(0);
1887 break;
1888 }
1889 case SP_ARG_VERB_LIST: {
1890 // This really shouldn't go here, we should init the app.
1891 // But, since we're just exiting in this path, there is
1892 // no harm, and this is really a better place to put
1893 // everything else.
1894 Inkscape::Extension::init();
1895 Inkscape::Verb::list();
1896 exit(0);
1897 break;
1898 }
1899 case SP_ARG_VERB:
1900 case SP_ARG_SELECT: {
1901 gchar const *arg = poptGetOptArg(ctx);
1902 if (arg != NULL) {
1903 // printf("Adding in: %s\n", arg);
1904 new Inkscape::CmdLineAction((a == SP_ARG_VERB), arg);
1905 }
1906 break;
1907 }
1908 case POPT_ERROR_BADOPT: {
1909 g_warning ("Invalid option %s", poptBadOption(ctx, 0));
1910 exit(1);
1911 break;
1912 }
1913 default: {
1914 break;
1915 }
1916 }
1917 }
1919 gchar const ** const args = poptGetArgs(ctx);
1920 if (args != NULL) {
1921 for (unsigned i = 0; args[i] != NULL; i++) {
1922 fl = g_slist_append(fl, g_strdup(args[i]));
1923 }
1924 }
1926 return fl;
1927 }
1930 /*
1931 Local Variables:
1932 mode:c++
1933 c-file-style:"stroustrup"
1934 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1935 indent-tabs-mode:nil
1936 fill-column:99
1937 End:
1938 */
1939 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :