1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * File export from the command line. This code, greatly modified, use to be in main.cpp. It should
4  * be replaced by code shared with the file dialog (Gio::Actions?).
5  *
6  * Copyright (C) 2018 Tavmjong Bah
7  *
8  * Git blame shows that bulia byak is the main author of the original export code from
9  * main.cpp. Other authors of note include Nicolas Dufour, Vinicius dos Santos Oliveira, and Bob
10  * Jamison; none of whom bothered to add their names to the copyright of main.cc.
11  *
12  * The contents of this file may be used under the GNU General Public License Version 2 or later.
13  *
14  */
15 
16 #include "file-export-cmd.h"
17 
18 #include <boost/algorithm/string.hpp>
19 #include <png.h> // PNG export
20 
21 #include "document.h"
22 #include "object/object-set.h"
23 #include "object/sp-item.h"
24 #include "object/sp-root.h"
25 #include "object/sp-text.h"
26 #include "object/sp-flowtext.h"
27 #include "object/sp-namedview.h"
28 #include "object/sp-object-group.h"
29 #include "path-chemistry.h" // sp_item_list_to_curves
30 #include "text-editing.h" // te_update_layout_now_recursive
31 #include "selection-chemistry.h" // fit_canvas_to_drawing
32 #include "svg/svg-color.h" // Background color
33 #include "helper/png-write.h" // PNG Export
34 
35 #include "extension/extension.h"
36 #include "extension/system.h"
37 #include "extension/db.h"
38 #include "extension/output.h"
39 #include "extension/init.h"
40 
41 
42 // Temporary dependency : once all compilers we want to support have support for
43 // C++17 std::filesystem (with #include <filesystem> ) then we drop this dep
44 // (Dev meeting, 2020-09-25)
45 
46 #include <boost/filesystem.hpp>
47 namespace filesystem = boost::filesystem;
48 
InkFileExportCmd()49 InkFileExportCmd::InkFileExportCmd()
50     : export_overwrite(false)
51     , export_area_drawing(false)
52     , export_area_page(false)
53     , export_margin(0)
54     , export_area_snap(false)
55     , export_use_hints(false)
56     , export_width(0)
57     , export_height(0)
58     , export_dpi(0)
59     , export_ignore_filters(false)
60     , export_text_to_path(false)
61     , export_ps_level(3)
62     , export_pdf_level("1.5")
63     , export_latex(false)
64     , export_id_only(false)
65     , export_background_opacity(-1) // default is unset != actively set to 0
66     , export_plain_svg(false)
67 {
68 }
69 
70 void
do_export(SPDocument * doc,std::string filename_in)71 InkFileExportCmd::do_export(SPDocument* doc, std::string filename_in)
72 {
73     std::string export_type_filename;
74     std::vector<Glib::ustring> export_type_list;
75 
76     // Get export type from filename supplied with --export-filename
77     if (!export_filename.empty() && export_filename != "-") {
78         auto fn = filesystem::path(export_filename);
79         if (!fn.has_extension()) {
80             if (export_type.empty() && export_extension.empty()) {
81                 std::cerr << "InkFileExportCmd::do_export: No export type specified. "
82                           << "Append a supported file extension to filename provided with --export-filename or "
83                           << "provide one or more extensions separately using --export-type" << std::endl;
84                 return;
85             } else {
86                 // no extension is fine if --export-type is given
87                 // explicitly stated extensions are handled later
88             }
89         } else {
90             export_type_filename = fn.extension().string().substr(1);
91             boost::algorithm::to_lower(export_type_filename);
92             export_filename = (fn.parent_path() / fn.stem()).string();
93         }
94     }
95 
96     // Get export type(s) from string supplied with --export-type
97     if (!export_type.empty()) {
98         export_type_list = Glib::Regex::split_simple("[,;]", export_type);
99     }
100 
101     // Determine actual type(s) for export.
102     if (export_use_hints) {
103         // Override type if --export-use-hints is used (hints presume PNG export for now)
104         // TODO: There's actually no reason to presume. We could allow to export to any format using hints!
105         if (export_id.empty() && !export_area_drawing) {
106             std::cerr << "InkFileExportCmd::do_export: "
107                       << "--export-use-hints can only be used with --export-id or --export-area-drawing." << std::endl;
108             return;
109         }
110         if (export_type_list.size() > 1 || (export_type_list.size() == 1 && export_type_list[0] != "png")) {
111             std::cerr << "InkFileExportCmd::do_export: --export-use-hints can only be used with PNG export! "
112                       << "Ignoring --export-type=" << export_type << "." << std::endl;
113         }
114         if (!export_filename.empty()) {
115             std::cerr << "InkFileExportCmd::do_export: --export-filename is ignored when using --export-use-hints!" << std::endl;
116         }
117         export_type_list.clear();
118         export_type_list.emplace_back("png");
119     } else if (export_type_list.empty()) {
120         if (!export_type_filename.empty()) {
121             export_type_list.emplace_back(export_type_filename); // use extension from filename
122         } else if (!export_extension.empty()) {
123             // guess export type from extension
124             auto ext =
125                 dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(export_extension.data()));
126             if (ext) {
127                 export_type_list.emplace_back(std::string(ext->get_extension()).substr(1));
128             } else {
129                 std::cerr << "InkFileExportCmd::do_export: "
130                           << "The supplied --export-extension was not found. Specify a file extension "
131                           << "to get a list of available extensions for this file type.";
132                 return;
133             }
134         } else {
135             export_type_list.emplace_back("svg"); // fall-back to SVG by default
136         }
137     }
138     // check if multiple export files are requested, but --export_extension was supplied
139     if (!export_extension.empty() && export_type_list.size() != 1) {
140         std::cerr
141             << "InkFileExportCmd::do_export: You may only specify one export type if --export-extension is supplied";
142         return;
143     }
144     Inkscape::Extension::DB::OutputList extension_list;
145     Inkscape::Extension::db.get_output_list(extension_list);
146 
147     for (auto const &Type : export_type_list) {
148         // use lowercase type for following comparisons
149         auto type = Type.lowercase();
150         g_info("exporting '%s' to type '%s'", filename_in.c_str(), type.c_str());
151 
152         export_type_current = type;
153 
154         // Check for consistency between extension of --export-filename and --export-type if both are given
155         if (!export_type_filename.empty() && (type != export_type_filename)) {
156             std::cerr << "InkFileExportCmd::do_export: "
157                       << "Ignoring extension of export filename (" << export_type_filename << ") "
158                       << "as it does not match the current export type (" << type << ")." << std::endl;
159         }
160         bool export_extension_forced = !export_extension.empty();
161         // For PNG export, there is no extension, so the method below can not be used.
162         if (type == "png") {
163             if (!export_extension_forced) {
164                 do_export_png(doc, filename_in);
165             } else {
166                 std::cerr << "InkFileExportCmd::do_export: "
167                           << "The parameter --export-extension is invalid for PNG export" << std::endl;
168             }
169             continue;
170         }
171         // for SVG export, we let the do_export_svg function handle the selection of the extension, unless
172         // an extension ID was explicitly given. This makes handling of --export-plain-svg easier (which
173         // should also work when multiple file types are given, unlike --export-extension)
174         if (type == "svg" && !export_extension_forced) {
175             do_export_svg(doc, filename_in);
176             continue;
177         }
178 
179         bool extension_for_fn_exists = false;
180         bool exported = false;
181         // if no extension is found, the entire list of extensions is walked through,
182         // so we can use the same loop to construct the list of available formats for the error messsage
183         std::list<std::string> filetypes({".svg", ".png", ".ps", ".eps", ".pdf"});
184         std::list<std::string> exts_for_fn;
185         for (auto oext : extension_list) {
186             if (oext->deactivated()) {
187                 continue;
188             }
189             auto name = Glib::ustring(oext->get_extension()).lowercase();
190             filetypes.emplace_back(name);
191             if (name == "." + type) {
192                 extension_for_fn_exists = true;
193                 exts_for_fn.emplace_back(oext->get_id());
194                 if (!export_extension_forced ||
195                     (export_extension_forced && export_extension == Glib::ustring(oext->get_id()).lowercase())) {
196                     if (type == "svg") {
197                         do_export_svg(doc, filename_in, *oext);
198                     } else if (type == "ps") {
199                         do_export_ps_pdf(doc, filename_in, "image/x-postscript", *oext);
200                     } else if (type == "eps") {
201                         do_export_ps_pdf(doc, filename_in, "image/x-e-postscript", *oext);
202                     } else if (type == "pdf") {
203                         do_export_ps_pdf(doc, filename_in, "application/pdf", *oext);
204                     } else {
205                         do_export_extension(doc, filename_in, oext);
206                     }
207                     exported = true;
208                     break;
209                 }
210             }
211         }
212         if (!exported) {
213             if (export_extension_forced && extension_for_fn_exists) {
214                 // the located extension for this file type did not match the provided --export-extension parameter
215                 std::cerr << "InkFileExportCmd::do_export: "
216                           << "The supplied extension ID (" << export_extension
217                           << ") does not match any of the extensions "
218                           << "available for this file type." << std::endl
219                           << "Supported IDs for this file type: [";
220                 copy(exts_for_fn.begin(), exts_for_fn.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
221                 std::cerr << "\b\b]" << std::endl;
222             } else {
223                 std::cerr << "InkFileExportCmd::do_export: Unknown export type: " << type << ". Allowed values: [";
224                 filetypes.sort();
225                 filetypes.unique();
226                 copy(filetypes.begin(), filetypes.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
227                 std::cerr << "\b\b]" << std::endl;
228             }
229         }
230     }
231 }
232 
233 // File names use std::string. HTML5 and presumably SVG 2 allows UTF-8 characters. Do we need to convert "object_id" here?
234 std::string
get_filename_out(std::string filename_in,std::string object_id)235 InkFileExportCmd::get_filename_out(std::string filename_in, std::string object_id)
236 {
237     // Pipe out
238     if (export_filename == "-") {
239         return "-";
240     }
241 
242     auto const export_type_current_native = Glib::filename_from_utf8(export_type_current);
243 
244     // Use filename provided with --export-filename if given (and append proper extension).
245     if (!export_filename.empty()) {
246         return export_filename + "." + export_type_current_native;
247     }
248 
249     // Check for pipe
250     if (filename_in == "-") {
251         return "-";
252     }
253 
254     // Construct output filename from input filename and export_type.
255     auto extension_pos = filename_in.find_last_of('.');
256     if (extension_pos == std::string::npos) {
257         std::cerr << "InkFileExportCmd::get_filename_out: cannot determine input file type from filename extension: " << filename_in << std::endl;
258         return (std::string());
259     }
260 
261     std::string extension = filename_in.substr(extension_pos+1);
262     if (export_overwrite && export_type_current_native == extension) {
263         return filename_in;
264     } else {
265         std::string tag;
266         if (export_type_current_native == extension) {
267             tag = "_out";
268         }
269         if (!object_id.empty()) {
270             tag = "_" + object_id;
271         }
272         return filename_in.substr(0, extension_pos) + tag + "." + export_type_current_native;
273     }
274 
275     // We need a valid file name to write to unless we're using PNG export hints.
276     // if (!(export_type == "png" && export_use_hints)) {
277 
278     //     // Check for file name.
279     //     if (filename_out.empty()) {
280     //         std::cerr << "InkFileExportCmd::do_export: Could not determine output file name!" << std::endl;
281     //         return (std::string());
282     //     }
283 
284     //     // Check if directory exists.
285     //     std::string directory = Glib::path_get_dirname(filename_out);
286     //     if (!Glib::file_test(directory, Glib::FILE_TEST_IS_DIR)) {
287     //         std::cerr << "InkFileExportCmd::do_export: File path includes directory that does not exist! " << directory << std::endl;
288     //         return (std::string());
289     //     }
290     // }
291 }
292 /**
293  *  Perform an SVG export
294  *
295  *  \param doc Document to export.
296  *  \param filename_in Filename for export
297  */
do_export_svg(SPDocument * doc,std::string const & filename_in)298 int InkFileExportCmd::do_export_svg(SPDocument *doc, std::string const &filename_in)
299 {
300     Inkscape::Extension::Output *oext;
301     if (export_plain_svg) {
302         oext =
303             dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get("org.inkscape.output.svg.plain"));
304     } else {
305         oext = dynamic_cast<Inkscape::Extension::Output *>(
306             Inkscape::Extension::db.get("org.inkscape.output.svg.inkscape"));
307     }
308     return do_export_svg(doc, filename_in, *oext);
309 }
310 /**
311  *  Perform an SVG export
312  *
313  *  \param doc Document to export.
314  *  \param filename_in Filename for export
315  *  \param extension Output extension used for exporting
316  */
do_export_svg(SPDocument * doc,std::string const & filename_in,Inkscape::Extension::Output & extension)317 int InkFileExportCmd::do_export_svg(SPDocument *doc, std::string const &filename_in,
318                                     Inkscape::Extension::Output &extension)
319 {
320     // Start with options that are once per document.
321     if (export_text_to_path) {
322         std::vector<SPItem*> items;
323         SPRoot *root = doc->getRoot();
324         doc->ensureUpToDate();
325         for (auto& iter: root->children) {
326             SPItem* item = (SPItem*) &iter;
327             if (! (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item) || SP_IS_GROUP(item))) {
328                 continue;
329             }
330 
331             te_update_layout_now_recursive(item);
332             items.push_back(item);
333         }
334 
335         std::vector<SPItem*> selected;  // Not used
336         std::vector<Inkscape::XML::Node*> to_select;  // Not used
337 
338         sp_item_list_to_curves(items, selected, to_select);
339     }
340 
341     if (export_margin != 0) {
342         gdouble margin = export_margin;
343         doc->ensureUpToDate();
344         SPNamedView *nv;
345         Inkscape::XML::Node *nv_repr;
346         if ((nv = sp_document_namedview(doc, nullptr)) && (nv_repr = nv->getRepr())) {
347             sp_repr_set_svg_double(nv_repr, "fit-margin-top", margin);
348             sp_repr_set_svg_double(nv_repr, "fit-margin-left", margin);
349             sp_repr_set_svg_double(nv_repr, "fit-margin-right", margin);
350             sp_repr_set_svg_double(nv_repr, "fit-margin-bottom", margin);
351         }
352     }
353 
354     if (export_area_drawing) {
355         fit_canvas_to_drawing(doc, export_margin != 0 ? true : false);
356     } else if (export_area_page || export_id.empty() ) {
357         if (export_margin) {
358             doc->ensureUpToDate();
359             doc->fitToRect(*(doc->preferredBounds()), true);
360         }
361     }
362 
363 
364     // Export each object in list (or root if empty).  Use ';' so in future it could be possible to selected multiple objects to export together.
365     std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
366     if (objects.empty()) {
367         objects.emplace_back(); // So we do loop at least once for root.
368     }
369 
370     for (auto object : objects) {
371 
372         std::string filename_out = get_filename_out(filename_in, Glib::filename_from_utf8(object));
373         if (filename_out.empty()) {
374             return 1;
375         }
376 
377         if(!object.empty()) {
378             doc->ensureUpToDate();
379 
380             // "crop" the document to the specified object, cleaning as we go.
381             SPObject *obj = doc->getObjectById(object);
382             if (obj == nullptr) {
383                 std::cerr << "InkFileExportCmd::do_export_svg: Object " << object << " not found in document, nothing to export." << std::endl;
384                 return 1;
385             }
386             if (export_id_only) {
387                 // If -j then remove all other objects to complete the "crop"
388                 doc->getRoot()->cropToObject(obj);
389             }
390             if (!(export_area_page || export_area_drawing)) {
391                 Inkscape::ObjectSet s(doc);
392                 s.set(obj);
393                 s.fitCanvas(export_margin ? true : false);
394             }
395         }
396         g_assert(std::string(extension.get_extension()) == ".svg");
397         try {
398             Inkscape::Extension::save(dynamic_cast<Inkscape::Extension::Extension *>(&extension), doc,
399                                       filename_out.c_str(), false, false, false,
400                                       export_plain_svg ? Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY
401                                                        : Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG);
402         } catch (Inkscape::Extension::Output::save_failed &e) {
403             std::cerr << "InkFileExportCmd::do_export_svg: Failed to save " << (export_plain_svg ? "" : "Inkscape")
404                       << " SVG to: " << filename_out << std::endl;
405             return 1;
406         }
407     }
408     return 0;
409 }
410 
get_bgcolor(SPDocument * doc)411 guint32 InkFileExportCmd::get_bgcolor(SPDocument *doc) {
412     guint32 bgcolor = 0x00000000;
413     if (!export_background.empty()) {
414         // override the page color
415         bgcolor = sp_svg_read_color(export_background.c_str(), 0xffffff00);
416         // default is opaque if a color is given on commandline
417         if (export_background_opacity < -.5 ) {
418             export_background_opacity = 255;
419         }
420     } else {
421         // read from namedview
422         Inkscape::XML::Node *nv = doc->getReprNamedView();
423         if (nv && nv->attribute("pagecolor")){
424             bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
425         }
426     }
427 
428     if (export_background_opacity > -.5) { // if the value is manually set
429         if (export_background_opacity > 1.0) {
430             float value = CLAMP (export_background_opacity, 1.0f, 255.0f);
431             bgcolor |= (guint32) floor(value);
432         } else {
433             float value = CLAMP (export_background_opacity, 0.0f, 1.0f);
434             bgcolor |= SP_COLOR_F_TO_U(value);
435         }
436     } else {
437         Inkscape::XML::Node *nv = doc->getReprNamedView();
438         if (nv && nv->attribute("inkscape:pageopacity")){
439             double opacity = 1.0;
440             sp_repr_get_double (nv, "inkscape:pageopacity", &opacity);
441             bgcolor |= SP_COLOR_F_TO_U(opacity);
442         } // else it's transparent
443     }
444     return bgcolor;
445 }
446 
447 /**
448  *  Perform a PNG export
449  *
450  *  \param doc Document to export.
451  *  \param filename_in filename for export
452  */
453 int
do_export_png(SPDocument * doc,std::string const & filename_in)454 InkFileExportCmd::do_export_png(SPDocument *doc, std::string const &filename_in)
455 {
456     bool filename_from_hint = false;
457     gdouble dpi = 0.0;
458     guint32 bgcolor = get_bgcolor(doc);
459 
460     // Export each object in list (or root if empty).  Use ';' so in future it could be possible to selected multiple objects to export together.
461     std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
462     if (objects.empty()) {
463         objects.emplace_back(); // So we do loop at least once for root.
464     }
465 
466     for (auto object_id : objects) {
467 
468         std::string filename_out = get_filename_out(filename_in, Glib::filename_from_utf8(object_id));
469 
470         std::vector<SPItem*> items;
471 
472         // Find export object. (Either root or object with specified id.)
473         SPObject *object = doc->getRoot();
474         if (!object_id.empty()) {
475             object = doc->getObjectById(object_id);
476         }
477 
478         if (!object) {
479             std::cerr << "InkFileExport::do_export_png: "
480                       << "Object with id=\"" << object_id
481                       << "\" was not found in the document. Skipping." << std::endl;
482             continue;
483         }
484 
485         if (!SP_IS_ITEM (object)) {
486             std::cerr << "InkFileExportCmd::do_export_png: "
487                       << "Object with id=\"" << object_id
488                       << "\" is not a visible item. Skipping." << std::endl;
489             continue;
490         }
491 
492         items.push_back(SP_ITEM(object)); // There is only one item, why do this?
493 
494         if (export_id_only) {
495             std::cerr << "Exporting only object with id=\""
496                       << object_id << "\"; all other objects hidden." << std::endl;
497         }
498 
499         // Find file name and dpi from hints.
500         if (export_use_hints) {
501 
502             // Retrieve export filename hint.
503             const gchar *fn_hint = object->getRepr()->attribute("inkscape:export-filename");
504             if (fn_hint) {
505                 filename_out = fn_hint;
506                 filename_from_hint = true;
507             } else {
508                 std::cerr << "InkFileExport::do_export_png: "
509                           << "Export filename hint not found for object " << object_id << ". Skipping." << std::endl;
510                 continue;
511             }
512 
513             // Retrieve export dpi hint. Only xdpi as ydpi is always the same now.
514             const gchar *dpi_hint = object->getRepr()->attribute("inkscape:export-xdpi");
515             if (dpi_hint) {
516                 if (export_dpi || export_width || export_height) {
517                     std::cerr << "InkFileExport::do_export_png: "
518                               << "Using bitmap dimensions from the command line "
519                               << "(--export-dpi, --export-width, or --export-height). "
520                               << "DPI hint " << dpi_hint << " is ignored." << std::endl;
521                 } else {
522                     dpi = g_ascii_strtod(dpi_hint, nullptr);
523                 }
524             } else {
525                 std::cerr << "InkFileExport::do_export_png: "
526                           << "Export DPI hint not found for the object." << std::endl;
527             }
528         }
529 
530         // ------------------------- File name -------------------------
531 
532         // Check we have a filename.
533         if (filename_out.empty()) {
534             std::cerr << "InkFileExport::do_export_png: "
535                       << "No valid export filename given and no filename hint. Skipping." << std::endl;
536             continue;
537         }
538 
539         if (filename_from_hint) {
540             //Make relative paths go from the document location, if possible:
541             if (!Glib::path_is_absolute(filename_out) && doc->getDocumentURI()) {
542                 std::string dirname = Glib::path_get_dirname(doc->getDocumentURI());
543                 if (!dirname.empty()) {
544                     filename_out = Glib::build_filename(dirname, filename_out);
545                 }
546             }
547         }
548 
549         // Check if directory exists
550         std::string directory = Glib::path_get_dirname(filename_out);
551         if (!Glib::file_test(directory, Glib::FILE_TEST_IS_DIR)) {
552             std::cerr << "File path " << filename_out << " includes directory that doesn't exist. Skipping." << std::endl;
553             continue;
554         }
555 
556         // -------------------------- DPI -------------------------------
557 
558         if (export_dpi != 0.0 && dpi == 0.0) {
559             dpi = export_dpi;
560             if ((dpi < 0.1) || (dpi > 10000.0)) {
561                 std::cerr << "InkFileExport::do_export_png: "
562                           << "DPI value " << export_dpi
563                           << " out of range [0.1 - 10000.0]. Skipping.";
564                 continue;
565             }
566         }
567 
568         // default dpi
569         if (dpi == 0.0) {
570             dpi = Inkscape::Util::Quantity::convert(1, "in", "px");
571         }
572 
573         // -------------------------  Area -------------------------------
574 
575         Geom::Rect area;
576         doc->ensureUpToDate();
577 
578         // Three choices: 1. Command-line export_area  2. Page area  3. Drawing area
579         if (!export_area.empty()) {
580 
581             // Export area command-line
582 
583             /* Try to parse area (given in SVG pixels) */
584             gdouble x0,y0,x1,y1;
585             if (sscanf(export_area.c_str(), "%lg:%lg:%lg:%lg", &x0, &y0, &x1, &y1) != 4) {
586                 g_warning("Cannot parse export area '%s'; use 'x0:y0:x1:y1'. Nothing exported.", export_area.c_str());
587                 return 1; // If it fails once, it will fail for all objects.
588             }
589             area = Geom::Rect(Geom::Interval(x0,x1), Geom::Interval(y0,y1));
590 
591         } else if (export_area_page || (!export_area_drawing && object_id.empty())) {
592 
593             // Export area page (explicit or if no object is given).
594             Geom::Point origin(doc->getRoot()->x.computed, doc->getRoot()->y.computed);
595             area = Geom::Rect(origin, origin + doc->getDimensions());
596 
597         } else {
598 
599             // Export area drawing (explicit or if object is given).
600             Geom::OptRect areaMaybe = static_cast<SPItem *>(object)->documentVisualBounds();
601             if (areaMaybe) {
602                 area = *areaMaybe;
603             } else {
604                 std::cerr << "InkFileExport::do_export_png: "
605                           << "Unable to determine a valid bounding box. Skipping." << std::endl;
606                 continue;
607             }
608         }
609 
610         if (export_area_snap) {
611             area = area.roundOutwards();
612         }
613         // End finding area.
614 
615         // -------------------------- Width and Height ---------------------------------
616 
617         unsigned long int width = 0;
618         unsigned long int height = 0;
619         double xdpi = dpi;
620         double ydpi = dpi;
621 
622         if (export_height != 0) {
623             height = export_height;
624             if ((height < 1) || (height > PNG_UINT_31_MAX)) {
625                 std::cerr << "InkFileExport::do_export_png: "
626                           << "Export height " << height << " out of range (1 to " << PNG_UINT_31_MAX << ")" << std::endl;
627                 continue;
628             }
629             ydpi = Inkscape::Util::Quantity::convert(height, "in", "px") / area.height();
630             xdpi = ydpi;
631             dpi = ydpi;
632         }
633 
634         if (export_width != 0) {
635             width = export_width;
636             if ((width < 1) || (width > PNG_UINT_31_MAX)) {
637                 std::cerr << "InkFileExport::do_export_png: "
638                           << "Export width " << width << " out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
639                 continue;
640             }
641             xdpi = Inkscape::Util::Quantity::convert(width, "in", "px") / area.width();
642             ydpi = export_height ? ydpi : xdpi;
643             dpi = xdpi;
644         }
645 
646         if (width == 0) {
647             width = (unsigned long int) (Inkscape::Util::Quantity::convert(area.width(), "px", "in") * dpi + 0.5);
648         }
649 
650         if (height == 0) {
651             height = (unsigned long int) (Inkscape::Util::Quantity::convert(area.height(), "px", "in") * dpi + 0.5);
652         }
653 
654         if ((width < 1) || (height < 1) || (width > PNG_UINT_31_MAX) || (height > PNG_UINT_31_MAX)) {
655             std::cerr << "InkFileExport::do_export_png: Dimensions " << width << "x" << height << " are out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
656             continue;
657         }
658 
659         // -------------------------- Bit Depth and Color Type --------------------
660 
661         int bit_depth = 8; // default of sp_export_png_file function
662         int color_type = PNG_COLOR_TYPE_RGB_ALPHA; // default of sp_export_png_file function
663 
664         if (!export_png_color_mode.empty()) {
665             // data as in ui/dialog/export.cpp:
666             const std::map<std::string, std::pair<int, int>> color_modes = {
667                 {"Gray_1", {PNG_COLOR_TYPE_GRAY, 1}},
668                 {"Gray_2", {PNG_COLOR_TYPE_GRAY, 2}},
669                 {"Gray_4", {PNG_COLOR_TYPE_GRAY, 4}},
670                 {"Gray_8", {PNG_COLOR_TYPE_GRAY, 8}},
671                 {"Gray_16", {PNG_COLOR_TYPE_GRAY, 16}},
672                 {"RGB_8", {PNG_COLOR_TYPE_RGB, 8}},
673                 {"RGB_16", {PNG_COLOR_TYPE_RGB, 16}},
674                 {"GrayAlpha_8", {PNG_COLOR_TYPE_GRAY_ALPHA, 8}},
675                 {"GrayAlpha_16", {PNG_COLOR_TYPE_GRAY_ALPHA, 16}},
676                 {"RGBA_8", {PNG_COLOR_TYPE_RGB_ALPHA, 8}},
677                 {"RGBA_16", {PNG_COLOR_TYPE_RGB_ALPHA, 16}},
678             };
679             auto it = color_modes.find(export_png_color_mode);
680             if (it == color_modes.end()) {
681                 std::cerr << "InkFileExport::do_export_png: "
682                           << "Color mode " << export_png_color_mode << " is invalid. It must be one of Gray_1/Gray_2/Gray_4/Gray_8/Gray_16/RGB_8/RGB_16/GrayAlpha_8/GrayAlpha_16/RGBA_8/RGBA_16." << std::endl;
683                 continue;
684             } else {
685                 std::tie(color_type, bit_depth) = it->second;
686             }
687         }
688 
689         // ----------------------  Generate the PNG -------------------------------
690 
691         // Do we really need to print this?
692         std::cerr << "Background RRGGBBAA: " << std::hex << bgcolor << std::dec << std::endl;
693         std::cerr << "Area "
694                   << area[Geom::X][0] << ":" << area[Geom::Y][0] << ":"
695                   << area[Geom::X][1] << ":" << area[Geom::Y][1] << " exported to "
696                   << width << " x " << height << " pixels (" << dpi << " dpi)" << std::endl;
697 
698         reverse(items.begin(),items.end()); // But there was only one item!
699 
700         if( sp_export_png_file(doc, filename_out.c_str(), area, width, height, xdpi, ydpi,
701                                bgcolor, nullptr, nullptr, true, export_id_only ? items : std::vector<SPItem*>(),
702                                false, color_type, bit_depth) == 1 ) {
703         } else {
704             std::cerr << "InkFileExport::do_export_png: Failed to export to " << filename_out << std::endl;
705             continue;
706         }
707 
708     } // End loop over objects.
709     return 0;
710 }
711 
712 
713 /**
714  *  Perform a PDF/PS/EPS export
715  *
716  *  \param doc Document to export.
717  *  \param filename File to write to.
718  *  \param mime MIME type to export as.
719  */
720 int
do_export_ps_pdf(SPDocument * doc,std::string const & filename_in,std::string mime_type)721 InkFileExportCmd::do_export_ps_pdf(SPDocument* doc, std::string const &filename_in, std::string mime_type)
722 {
723     // Check if we support mime type.
724     Inkscape::Extension::DB::OutputList o;
725     Inkscape::Extension::db.get_output_list(o);
726     Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
727     while (i != o.end() && strcmp( (*i)->get_mimetype(), mime_type.c_str() ) != 0) {
728         ++i;
729     }
730 
731     if (i == o.end()) {
732         std::cerr << "InkFileExportCmd::do_export_ps_pdf: Could not find an extension to export to MIME type: " << mime_type << std::endl;
733         return 1;
734     }
735     return do_export_ps_pdf(doc, filename_in, mime_type, *dynamic_cast<Inkscape::Extension::Output *>(*i));
736 }
737 
738 /**
739  *  Perform a PDF/PS/EPS export
740  *
741  *  \param doc Document to export.
742  *  \param filename File to write to.
743  *  \param mime MIME type to export as.
744  *  \param Extension used for exporting
745  */
do_export_ps_pdf(SPDocument * doc,std::string const & filename_in,std::string mime_type,Inkscape::Extension::Output & extension)746 int InkFileExportCmd::do_export_ps_pdf(SPDocument *doc, std::string const &filename_in, std::string mime_type,
747                                        Inkscape::Extension::Output &extension)
748 {
749     // check if the passed extension conforms to the mime type.
750     assert(std::string(extension.get_mimetype()) == mime_type);
751     // Start with options that are once per document.
752 
753     // Set export options.
754     if (export_text_to_path) {
755         extension.set_param_optiongroup("textToPath", "paths");
756     } else if (export_latex) {
757         extension.set_param_optiongroup("textToPath", "LaTeX");
758     } else {
759         extension.set_param_optiongroup("textToPath", "embed");
760     }
761 
762     if (export_ignore_filters) {
763         extension.set_param_bool("blurToBitmap", false);
764     } else {
765         extension.set_param_bool("blurToBitmap", true);
766 
767         gdouble dpi = 96.0;
768         if (export_dpi) {
769             dpi = export_dpi;
770             if ((dpi < 1) || (dpi > 10000.0)) {
771                 g_warning("DPI value %lf out of range [1 - 10000]. Using 96 dpi instead.", export_dpi);
772                 dpi = 96;
773             }
774         }
775 
776         extension.set_param_int("resolution", (int)dpi);
777     }
778 
779     extension.set_param_float("bleed", export_margin);
780 
781     // handle --export-pdf-version
782     if (mime_type == "application/pdf") {
783         bool set_export_pdf_version_fail = true;
784         const gchar *pdfver_param_name = "PDFversion";
785         if (!export_pdf_level.empty()) {
786             // combine "PDF " and the given command line
787             std::string version_gui_string = std::string("PDF-") + export_pdf_level.raw();
788             try {
789                 // first, check if the given pdf version is selectable in the ComboBox
790                 if (extension.get_param_optiongroup_contains("PDFversion", version_gui_string.c_str())) {
791                     extension.set_param_optiongroup(pdfver_param_name, version_gui_string.c_str());
792                     set_export_pdf_version_fail = false;
793                 } else {
794                     g_warning("Desired PDF export version \"%s\" not supported! Hint: input one of the versions found in the pdf export dialog e.g. \"1.4\".",
795                               export_pdf_level.c_str());
796                 }
797             } catch (...) {
798                 // can be thrown along the way:
799                 // throw Extension::param_not_exist();
800                 // throw Extension::param_not_enum_param();
801                 g_warning("Parameter or Enum \"%s\" might not exist", pdfver_param_name);
802             }
803         }
804 
805         // set default pdf export version to 1.4, also if something went wrong
806         if(set_export_pdf_version_fail) {
807             extension.set_param_optiongroup(pdfver_param_name, "PDF-1.4");
808         }
809     }
810 
811     if (mime_type == "image/x-postscript" || mime_type == "image/x-e-postscript") {
812         if ( export_ps_level < 2 || export_ps_level > 3 ) {
813             g_warning("Only supported PostScript levels are 2 and 3."
814                       " Defaulting to 2.");
815             export_ps_level = 2;
816         }
817 
818         extension.set_param_optiongroup("PSlevel", (export_ps_level == 3) ? "PS3" : "PS2");
819     }
820 
821 
822     // Export each object in list (or root if empty).  Use ';' so in future it could be possible to selected multiple objects to export together.
823     std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
824     if (objects.empty()) {
825         objects.emplace_back(); // So we do loop at least once for root.
826     }
827 
828     for (auto object : objects) {
829 
830         std::string filename_out = get_filename_out(filename_in, Glib::filename_from_utf8(object));
831         if (filename_out.empty()) {
832             return 1;
833         }
834 
835         // Export only object with given id.
836         if (!object.empty()) {
837             SPObject *o = doc->getObjectById(object);
838             if (o == nullptr) {
839                 std::cerr << "InkFileExportCmd::do_export_ps_pdf: Object " << object << " not found in document, nothing to export." << std::endl;
840                 return 1;
841             }
842             extension.set_param_string("exportId", object.c_str());
843         } else {
844             extension.set_param_string("exportId", "");
845         }
846 
847         // Set export area.
848         if (export_area_page && export_area_drawing) {
849             std::cerr << "You cannot use --export-area-page and --export-area-drawing at the same time; only the former will take effect." << std::endl;;
850             export_area_drawing = false;
851         }
852 
853         if (export_area_drawing) {
854             extension.set_param_optiongroup("area", "drawing");
855         }
856 
857         if (export_area_page) {
858             if (export_type == "eps") {
859                 std::cerr << "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." << std::endl;
860             }
861             extension.set_param_optiongroup("area", "page");
862         }
863 
864         if (!export_area_drawing && !export_area_page) {
865             // Neither is set.
866             if (export_type == "eps" || !object.empty()) {
867                 // Default to drawing for EPS or if object is specified (latter matches behavior for other export types).
868                 extension.set_param_optiongroup("area", "drawing");
869             } else {
870                 extension.set_param_optiongroup("area", "page");
871             }
872         }
873 
874         try {
875             extension.save(doc, filename_out.c_str());
876         } catch(...) {
877             std::cerr << "Failed to save PS/EPS/PDF to: " << filename_out << std::endl;
878             return 1;
879         }
880     }
881 
882     return 0;
883 }
884 
885 /**
886  *  Export a document using an export extension
887  *
888  *  \param doc Document to export.
889  *  \param filename to export to.
890  *  \param output extension used for export
891  */
do_export_extension(SPDocument * doc,std::string const & filename_in,Inkscape::Extension::Output * extension)892 int InkFileExportCmd::do_export_extension(SPDocument *doc, std::string const &filename_in,
893                                           Inkscape::Extension::Output *extension)
894 {
895     std::string filename_out = get_filename_out(filename_in);
896     if (extension) {
897         extension->set_state(Inkscape::Extension::Extension::STATE_LOADED);
898         extension->save(doc, filename_out.c_str());
899     }
900     return 0;
901 }
902 
903 /*
904   Local Variables:
905   mode:c++
906   c-file-style:"stroustrup"
907   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
908   indent-tabs-mode:nil
909   fill-column:99
910   End:
911 */
912 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
913