// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * File export from the command line. This code, greatly modified, use to be in main.cpp. It should
 * be replaced by code shared with the file dialog (Gio::Actions?).
 *
 * Copyright (C) 2018 Tavmjong Bah
 *
 * Git blame shows that bulia byak is the main author of the original export code from
 * main.cpp. Other authors of note include Nicolas Dufour, Vinicius dos Santos Oliveira, and Bob
 * Jamison; none of whom bothered to add their names to the copyright of main.cc.
 *
 * The contents of this file may be used under the GNU General Public License Version 2 or later.
 *
 */

#include "file-export-cmd.h"

#include <boost/algorithm/string.hpp>
#include <iostream>
#include <png.h> // PNG export
#include <string>

#include "document.h"
#include "extension/db.h"
#include "extension/extension.h"
#include "extension/init.h"
#include "extension/output.h"
#include "extension/system.h"
#include "helper/png-write.h" // PNG Export
#include "object/object-set.h"
#include "object/sp-flowtext.h"
#include "object/sp-item.h"
#include "object/sp-namedview.h"
#include "object/sp-object-group.h"
#include "object/sp-page.h"
#include "object/sp-root.h"
#include "object/sp-text.h"
#include "page-manager.h"
#include "path-chemistry.h"      // sp_item_list_to_curves
#include "selection-chemistry.h" // fit_canvas_to_drawing
#include "svg/svg-color.h"       // Background color
#include "text-editing.h"        // te_update_layout_now_recursive
#include "util/parse-int-range.h"
#include "io/sys.h"

// Temporary dependency : once all compilers we want to support have support for
// C++17 std::filesystem (with #include <filesystem> ) then we drop this dep
// (Dev meeting, 2020-09-25)

#ifdef G_OS_WIN32
#include <filesystem>
namespace filesystem = std::filesystem;
#else
#include <boost/filesystem.hpp>
namespace filesystem = boost::filesystem;
#endif

InkFileExportCmd::InkFileExportCmd()
    : export_overwrite(false)
    , export_margin(0)
    , export_area_snap(false)
    , export_use_hints(false)
    , export_width(0)
    , export_height(0)
    , export_dpi(0)
    , export_ignore_filters(false)
    , export_text_to_path(false)
    , export_ps_level(3)
    , export_pdf_level("1.5")
    , export_latex(false)
    , export_id_only(false)
    , export_background_opacity(-1) // default is unset != actively set to 0
    , export_plain_svg(false)
{
}

void
InkFileExportCmd::do_export(SPDocument* doc, std::string filename_in)
{
    std::string export_type_filename;
    std::vector<Glib::ustring> export_type_list;

    // Get export type from filename supplied with --export-filename
    if (!export_filename.empty() && export_filename != "-") {

        // Attempt to result variable and home path use in export filenames.
        Glib::RefPtr<Gio::File> gfile = Gio::File::create_for_parse_name(export_filename);
        export_filename = gfile->get_parse_name();

#ifdef G_OS_WIN32
        auto fn = filesystem::u8path(export_filename);
#else
        auto fn = filesystem::path(export_filename);
#endif
        if (!fn.has_extension()) {
            if (export_type.empty() && export_extension.empty()) {
                std::cerr << "InkFileExportCmd::do_export: No export type specified. "
                          << "Append a supported file extension to filename provided with --export-filename or "
                          << "provide one or more extensions separately using --export-type" << std::endl;
                return;
            } else {
                // no extension is fine if --export-type is given
                // explicitly stated extensions are handled later
            }
        } else {
            export_type_filename = fn.extension().string().substr(1);
            boost::algorithm::to_lower(export_type_filename);
            export_filename = (fn.parent_path() / fn.stem()).string();
        }
    }

    // Get export type(s) from string supplied with --export-type
    if (!export_type.empty()) {
        export_type_list = Glib::Regex::split_simple("[,;]", export_type);
    }

    // Determine actual type(s) for export.
    if (export_use_hints) {
        // Override type if --export-use-hints is used (hints presume PNG export for now)
        // TODO: There's actually no reason to presume. We could allow to export to any format using hints!
        if (export_id.empty() && export_area_type != ExportAreaType::Drawing) {
            std::cerr << "InkFileExportCmd::do_export: "
                      << "--export-use-hints can only be used with --export-id or --export-area-drawing." << std::endl;
            return;
        }
        if (export_type_list.size() > 1 || (export_type_list.size() == 1 && export_type_list[0] != "png")) {
            std::cerr << "InkFileExportCmd::do_export: --export-use-hints can only be used with PNG export! "
                      << "Ignoring --export-type=" << export_type.raw() << "." << std::endl;
        }
        if (!export_filename.empty()) {
            std::cerr << "InkFileExportCmd::do_export: --export-filename is ignored when using --export-use-hints!" << std::endl;
        }
        export_type_list.clear();
        export_type_list.emplace_back("png");
    } else if (export_type_list.empty()) {
        if (!export_type_filename.empty()) {
            export_type_list.emplace_back(export_type_filename); // use extension from filename
        } else if (!export_extension.empty()) {
            // guess export type from extension
            auto ext =
                dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(export_extension.data()));
            if (ext) {
                export_type_list.emplace_back(std::string(ext->get_extension()).substr(1));
            } else {
                std::cerr << "InkFileExportCmd::do_export: "
                          << "The supplied --export-extension was not found. Specify a file extension "
                          << "to get a list of available extensions for this file type.";
                return;
            }
        } else {
            export_type_list.emplace_back("svg"); // fall-back to SVG by default
        }
    }
    // check if multiple export files are requested, but --export_extension was supplied
    if (!export_extension.empty() && export_type_list.size() != 1) {
        std::cerr
            << "InkFileExportCmd::do_export: You may only specify one export type if --export-extension is supplied";
        return;
    }
    Inkscape::Extension::DB::OutputList extension_list;
    Inkscape::Extension::db.get_output_list(extension_list);

    // Export filename should be used when specified as the output file
    auto const filename_out = !export_filename.empty() ? export_filename : filename_in;

    for (auto const &Type : export_type_list) {
        // use lowercase type for following comparisons
        auto type = Type.lowercase();
        g_info("exporting '%s' to type '%s'", filename_in.c_str(), type.c_str());

        export_type_current = type;

        // Check for consistency between extension of --export-filename and --export-type if both are given
        if (!export_type_filename.empty() && (type != export_type_filename)) {
            std::cerr << "InkFileExportCmd::do_export: "
                      << "Ignoring extension of export filename (" << export_type_filename << ") "
                      << "as it does not match the current export type (" << type.raw() << ")." << std::endl;
        }
        bool export_extension_forced = !export_extension.empty();
        // For PNG export, there is no extension, so the method below can not be used.
        if (type == "png") {
            if (!export_extension_forced) {
                do_export_png(doc, filename_out);
            } else {
                std::cerr << "InkFileExportCmd::do_export: "
                          << "The parameter --export-extension is invalid for PNG export" << std::endl;
            }
            continue;
        }
        // for SVG export, we let the do_export_svg function handle the selection of the extension, unless
        // an extension ID was explicitly given. This makes handling of --export-plain-svg easier (which
        // should also work when multiple file types are given, unlike --export-extension)
        if (type == "svg" && !export_extension_forced) {
            do_export_svg(doc, filename_out);
            continue;
        }

        bool extension_for_fn_exists = false;
        bool exported = false;
        // if no extension is found, the entire list of extensions is walked through,
        // so we can use the same loop to construct the list of available formats for the error message
        std::list<std::string> filetypes({".svg", ".png", ".ps", ".eps", ".pdf"});
        std::list<std::string> exts_for_fn;
        for (auto oext : extension_list) {
            if (oext->deactivated()) {
                continue;
            }
            auto name = Glib::ustring(oext->get_extension()).lowercase();
            filetypes.emplace_back(name);
            if (name == "." + type) {
                extension_for_fn_exists = true;
                exts_for_fn.emplace_back(oext->get_id());
                if (!export_extension_forced ||
                    (export_extension == Glib::ustring(oext->get_id()).lowercase())) {
                    if (type == "svg") {
                        do_export_vector(doc, filename_out, *oext);
                    } else if (type == "ps") {
                        do_export_ps_pdf(doc, filename_out, "image/x-postscript", *oext);
                    } else if (type == "eps") {
                        do_export_ps_pdf(doc, filename_out, "image/x-e-postscript", *oext);
                    } else if (type == "pdf") {
                        do_export_ps_pdf(doc, filename_out, "application/pdf", *oext);
                    } else {
                        do_export_extension(doc, filename_out, oext);
                    }
                    exported = true;
                    break;
                }
            }
        }
        if (!exported) {
            if (export_extension_forced && extension_for_fn_exists) {
                // the located extension for this file type did not match the provided --export-extension parameter
                std::cerr << "InkFileExportCmd::do_export: "
                          << "The supplied extension ID (" << export_extension
                          << ") does not match any of the extensions "
                          << "available for this file type." << std::endl
                          << "Supported IDs for this file type: [";
                copy(exts_for_fn.begin(), exts_for_fn.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
                std::cerr << "\b\b]" << std::endl;
            } else {
                std::cerr << "InkFileExportCmd::do_export: Unknown export type: " << type.raw() << ". Allowed values: [";
                filetypes.sort();
                filetypes.unique();
                copy(filetypes.begin(), filetypes.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
                std::cerr << "\b\b]" << std::endl;
            }
        }
    }
}

// File names use std::string. HTML5 and presumably SVG 2 allows UTF-8 characters. Do we need to convert "object_id" here?
std::string
InkFileExportCmd::get_filename_out(std::string filename_in, std::string object_id)
{
    // Pipe out
    if (export_filename == "-") {
        return "-";
    }

    auto const export_type_current_native = Glib::filename_from_utf8(export_type_current);

    // Use filename provided with --export-filename if given
    if (!export_filename.empty()) {
        auto ext = Inkscape::IO::get_file_extension(export_filename);
        auto cmp = "." + export_type_current_native;
        return export_filename + (ext == cmp ? "" : cmp);
    }

    // Check for pipe
    if (filename_in == "-") {
        return "-";
    }

    // Construct output filename from input filename and export_type.
    auto extension_pos = filename_in.find_last_of('.');
    if (extension_pos == std::string::npos) {
        std::cerr << "InkFileExportCmd::get_filename_out: cannot determine input file type from filename extension: " << filename_in << std::endl;
        return (std::string());
    }

    std::string extension = filename_in.substr(extension_pos+1);
    if (export_overwrite && export_type_current_native == extension) {
        return filename_in;
    } else {
        std::string tag;
        if (export_type_current_native == extension) {
            tag = "_out";
        }
        if (!object_id.empty()) {
            tag = "_" + object_id;
        }
        return filename_in.substr(0, extension_pos) + tag + "." + export_type_current_native;
    }
}

/**
 *  Perform an SVG export
 *
 *  \param doc Document to export.
 *  \param export_filename Filename for export
 */
int InkFileExportCmd::do_export_svg(SPDocument *doc, std::string const &export_filename)
{
    Inkscape::Extension::Output *oext;
    if (export_plain_svg) {
        oext =
            dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get("org.inkscape.output.svg.plain"));
    } else {
        oext = dynamic_cast<Inkscape::Extension::Output *>(
            Inkscape::Extension::db.get("org.inkscape.output.svg.inkscape"));
    }
    return do_export_vector(doc, export_filename, *oext);
}
/**
 *  Perform a vector file export (SVG, PDF, or PS)
 *
 *  \param doc Document to export.
 *  \param export_filename Filename for export
 *  \param extension Output extension used for exporting
 */
int InkFileExportCmd::do_export_vector(SPDocument *doc, std::string const &export_filename,
                                       Inkscape::Extension::Output &extension)
{
    // Start with options that are once per document.
    if (export_text_to_path) {
        Inkscape::convert_text_to_curves(doc);
    }

    if (export_margin != 0) {
        gdouble margin = export_margin;
        doc->ensureUpToDate();
        SPNamedView *nv;
        Inkscape::XML::Node *nv_repr;
        if ((nv = doc->getNamedView()) && (nv_repr = nv->getRepr())) {
            nv_repr->setAttributeSvgDouble("fit-margin-top", margin);
            nv_repr->setAttributeSvgDouble("fit-margin-left", margin);
            nv_repr->setAttributeSvgDouble("fit-margin-right", margin);
            nv_repr->setAttributeSvgDouble("fit-margin-bottom", margin);
        }
    }

    if (export_area_type == ExportAreaType::Drawing) {
        fit_canvas_to_drawing(doc, export_margin != 0 ? true : false);
    } else if (export_area_type == ExportAreaType::Page || export_id.empty()) {
        if (export_margin) {
            doc->ensureUpToDate();
            doc->fitToRect(*(doc->preferredBounds()), export_margin);
        }
    }

    // Export pages instead of objects
    if (!export_page.empty()) {
        std::string base = export_filename;
        std::string ext = "svg";
        // Strip any possible extension
        std::string tmp_out = get_filename_out(export_filename, "");
        auto extension_pos = tmp_out.find_last_of('.');
        if (extension_pos != std::string::npos) {
            base = tmp_out.substr(0, extension_pos);
            ext = tmp_out.substr(extension_pos+1);
        }

        auto pages = Inkscape::parseIntRange(export_page);
        for (auto page_num : pages) {
            // And if only one page is selected then we assume the user knows the filename they intended.
            std::string filename_out = base + (pages.size() > 1 ? "_p" + std::to_string(page_num) : "") + "." + ext;

            auto copy_doc = doc->copy();
            copy_doc->prunePages(std::to_string(page_num), true);
            copy_doc->ensureUpToDate();
            copy_doc->vacuumDocument();

            try {
                extension.set_gui(false);
                Inkscape::Extension::save(dynamic_cast<Inkscape::Extension::Extension *>(&extension), copy_doc.get(),
                                          filename_out.c_str(), false, false, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY);
            } catch (Inkscape::Extension::Output::save_failed const &) {
                std::cerr << "InkFileExportCmd::do_export_vector: Failed to save " << (export_plain_svg ? "" : "Inkscape")
                          << " file to: " << filename_out << std::endl;
                return 1;
            }
        }
        return 0;
    }

    // Export each object in list (or root if empty).  Use ';' so in future it could be possible to selected multiple objects to export together.
    std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
    if (objects.empty()) {
        objects.emplace_back(); // So we do loop at least once for root.
    }

    for (auto object : objects) {
        auto copy_doc = doc->copy();

        std::string filename_out = get_filename_out(export_filename, Glib::filename_from_utf8(object));
        if (filename_out.empty()) {
            return 1;
        }

        if(!object.empty()) {
            copy_doc->ensureUpToDate();

            // "crop" the document to the specified object, cleaning as we go.
            SPObject *obj = copy_doc->getObjectById(object);
            if (obj == nullptr) {
                std::cerr << "InkFileExportCmd::do_export_vector: Object " << object.raw() << " not found in document, nothing to export." << std::endl;
                return 1;
            }
            if (export_id_only) {
                // If -j then remove all other objects to complete the "crop"
                copy_doc->getRoot()->cropToObject(obj);
            }
            if (export_area_type != ExportAreaType::Drawing && export_area_type != ExportAreaType::Page) {
                Inkscape::ObjectSet s(copy_doc.get());
                s.set(obj);
                s.fitCanvas((bool)export_margin);
            }
        }
        try {
            extension.set_gui(false);
            Inkscape::Extension::save(dynamic_cast<Inkscape::Extension::Extension *>(&extension), copy_doc.get(),
                                      filename_out.c_str(), false, false,
                                      export_plain_svg ? Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY
                                                       : Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG);
        } catch (Inkscape::Extension::Output::save_failed &e) {
            std::cerr << "InkFileExportCmd::do_export_vector: Failed to save " << (export_plain_svg ? "" : "Inkscape")
                      << " file to: " << filename_out << std::endl;
            return 1;
        }
    }
    return 0;
}

guint32 InkFileExportCmd::get_bgcolor(SPDocument *doc) {
    guint32 bgcolor = 0x00000000;
    if (!export_background.empty()) {
        // override the page color
        bgcolor = sp_svg_read_color(export_background.c_str(), 0xffffff00);
        // default is opaque if a color is given on commandline
        if (export_background_opacity < -.5 ) {
            export_background_opacity = 255;
        }
    } else {
        // read from namedview
        Inkscape::XML::Node *nv = doc->getReprNamedView();
        if (nv && nv->attribute("pagecolor")){
            bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
        }
    }

    if (export_background_opacity > -.5) { // if the value is manually set
        if (export_background_opacity > 1.0) {
            float value = CLAMP (export_background_opacity, 1.0f, 255.0f);
            bgcolor |= (guint32) floor(value);
        } else {
            float value = CLAMP (export_background_opacity, 0.0f, 1.0f);
            bgcolor |= SP_COLOR_F_TO_U(value);
        }
    } else {
        Inkscape::XML::Node *nv = doc->getReprNamedView();
        if (nv && nv->attribute("inkscape:pageopacity")){
            double opacity = nv->getAttributeDouble("inkscape:pageopacity", 1.0);
            bgcolor |= SP_COLOR_F_TO_U(opacity);
        } // else it's transparent
    }
    return bgcolor;
}

/**
 *  Perform a PNG export
 *
 *  \param doc Document to export.
 *  \param export_filename filename for export
 */
int
InkFileExportCmd::do_export_png(SPDocument *doc, std::string const &export_filename)
{
    bool filename_from_hint = false;
    gdouble dpi = 0.0;

    auto prefs = Inkscape::Preferences::get();
    bool old_dither = prefs->getBool("/options/dithering/value", true);
    prefs->setBool("/options/dithering/value", export_png_use_dithering);

    // Export each object in list (or root if empty).  Use ';' so in future it could be possible to selected multiple objects to export together.
    std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);

    std::vector<SPItem*> items;
    std::vector<Glib::ustring> objects_found;
    for (auto object_id : objects) {
        // Find export object. (Either root or object with specified id.)
        auto object = doc->getObjectById(object_id);

        if (!object) {
            std::cerr << "InkFileExport::do_export_png: "
                      << "Object with id=\"" << object_id.raw()
                      << "\" was not found in the document. Skipping." << std::endl;
            continue;
        }

        if (!is<SPItem>(object)) {
            std::cerr << "InkFileExportCmd::do_export_png: "
                      << "Object with id=\"" << object_id.raw()
                      << "\" is not a visible item. Skipping." << std::endl;
            continue;
        }

        items.push_back(cast<SPItem>(object));
        objects_found.push_back(object_id);
    }

    // Export pages instead of objects
    if (!export_page.empty()) {
        auto &pm = doc->getPageManager();
        std::string base = export_filename;
        // Strip any possible extension
        auto extension_pos = export_filename.find_last_of('.');
        if (extension_pos != std::string::npos)
            base = export_filename.substr(0, extension_pos);

        auto pages = Inkscape::parseIntRange(export_page);
        for (auto page_num : pages) {
            // We always use the png extension and ignore the extension given by the user
            // And if only one page is selected then we assume the user knows the filename they intended.
            std::string filename_out = base + (pages.size() > 1 ? "_p" + std::to_string(page_num) : "") + ".png";
            if (auto page = pm.getPage(page_num - 1)) {
                do_export_png_now(doc, filename_out, page->getDesktopRect(), dpi, items);
            }
        }
        return 0;
    }

    if (objects.empty()) {
        objects_found.emplace_back(); // So we do loop at least once for root.
    }

    for (auto object_id : objects_found) {
        SPObject *object = doc->getRoot();
        if (!object_id.empty()) {
            object = doc->getObjectById(object_id);
        }

        std::string filename_out = get_filename_out(export_filename, Glib::filename_from_utf8(object_id));

        if (export_id_only) {
            std::cerr << "Exporting only object with id=\""
                      << object_id.raw() << "\"; all other objects hidden." << std::endl;
        }

        // Find file name and dpi from hints.
        if (export_use_hints) {

            // Retrieve export filename hint.
            const gchar *fn_hint = object->getRepr()->attribute("inkscape:export-filename");
            if (fn_hint) {
                filename_out = fn_hint;
                filename_from_hint = true;
            } else {
                std::cerr << "InkFileExport::do_export_png: "
                          << "Export filename hint not found for object " << object_id.raw() << ". Skipping." << std::endl;
                continue;
            }

            // Retrieve export dpi hint. Only xdpi as ydpi is always the same now.
            const gchar *dpi_hint = object->getRepr()->attribute("inkscape:export-xdpi");
            if (dpi_hint) {
                if (export_dpi || export_width || export_height) {
                    std::cerr << "InkFileExport::do_export_png: "
                              << "Using bitmap dimensions from the command line "
                              << "(--export-dpi, --export-width, or --export-height). "
                              << "DPI hint " << dpi_hint << " is ignored." << std::endl;
                } else {
                    dpi = g_ascii_strtod(dpi_hint, nullptr);
                }
            } else {
                std::cerr << "InkFileExport::do_export_png: "
                          << "Export DPI hint not found for the object." << std::endl;
            }
        }

        // ------------------------- File name -------------------------

        // Check we have a filename.
        if (filename_out.empty()) {
            std::cerr << "InkFileExport::do_export_png: "
                      << "No valid export filename given and no filename hint. Skipping." << std::endl;
            continue;
        }

        if (filename_from_hint) {
            //Make relative paths go from the document location, if possible:
            if (!Glib::path_is_absolute(filename_out) && doc->getDocumentFilename()) {
                std::string dirname = Glib::path_get_dirname(doc->getDocumentFilename());
                if (!dirname.empty()) {
                    filename_out = Glib::build_filename(dirname, filename_out);
                }
            }
        }

        // Check if directory exists
        std::string directory = Glib::path_get_dirname(filename_out);
        if (!Glib::file_test(directory, Glib::FILE_TEST_IS_DIR)) {
            std::cerr << "File path " << filename_out << " includes directory that doesn't exist. Skipping." << std::endl;
            continue;
        }

        // -------------------------  Area -------------------------------

        Geom::Rect area;
        doc->ensureUpToDate();

        if (export_area_type == ExportAreaType::Unset) {
            // Default to drawing if has object, otherwise export page
            if (object_id.empty()) {
                export_area_type = ExportAreaType::Page;
            } else {
                export_area_type = ExportAreaType::Drawing;
            }
        }
        // Three choices: 1. Command-line export_area  2. Page area  3. Drawing area
        switch (export_area_type) {
            case ExportAreaType::Unset:
                std::cerr << "ExportAreaType::not_set should be handled before" << std::endl;
                return 1;
            case ExportAreaType::Page: {
                // Export area page (explicit or if no object is given).
                Geom::Point origin(doc->getRoot()->x.computed, doc->getRoot()->y.computed);
                area = Geom::Rect(origin, origin + doc->getDimensions());
                break;
            }
            case ExportAreaType::Area: {
                // Export area command-line

                /* Try to parse area (given in SVG pixels) */
                gdouble x0, y0, x1, y1;
                if (sscanf(export_area.c_str(), "%lg:%lg:%lg:%lg", &x0, &y0, &x1, &y1) != 4) {
                    g_warning("Cannot parse export area '%s'; use 'x0:y0:x1:y1'. Nothing exported.",
                              export_area.c_str());
                    return 1; // If it fails once, it will fail for all objects.
                }
                area = Geom::Rect(Geom::Interval(x0, x1), Geom::Interval(y0, y1));
                break;
            }
            case ExportAreaType::Drawing: {
                // Export area drawing (explicit or if object is given).
                Geom::OptRect areaMaybe = static_cast<SPItem *>(object)->documentVisualBounds();
                if (areaMaybe) {
                    area = *areaMaybe;
                } else {
                    std::cerr << "InkFileExport::do_export_png: "
                              << "Unable to determine a valid bounding box. Skipping." << std::endl;
                    continue;
                }
                break;
            }
        }

        if (export_area_snap) {
            area = area.roundOutwards();
        }
        // End finding area.
        do_export_png_now(doc, filename_out, area, dpi, items);

    } // End loop over objects.
    prefs->setBool("/options/dithering/value", old_dither);
    return 0;
}

void
InkFileExportCmd::do_export_png_now(SPDocument *doc, std::string const &filename_out, Geom::Rect area, double dpi_in, const std::vector<SPItem *> &items)
{
    // -------------------------- DPI -------------------------------

    double dpi = dpi_in;

    if (export_dpi != 0.0 && dpi == 0.0) {
        dpi = export_dpi;
        if ((dpi < 0.1) || (dpi > 10000.0)) {
            std::cerr << "InkFileExport::do_export_png: "
                      << "DPI value " << export_dpi
                      << " out of range [0.1 - 10000.0]. Skipping.";
            return;
        }
    }

    // default dpi
    if (dpi == 0.0) {
        dpi = Inkscape::Util::Quantity::convert(1, "in", "px");
    }

        // -------------------------- Width and Height ---------------------------------

        unsigned long int width = 0;
        unsigned long int height = 0;
        double xdpi = dpi;
        double ydpi = dpi;

        if (export_height != 0) {
            height = export_height;
            if ((height < 1) || (height > PNG_UINT_31_MAX)) {
                std::cerr << "InkFileExport::do_export_png: "
                          << "Export height " << height << " out of range (1 to " << PNG_UINT_31_MAX << ")" << std::endl;
                return;
            }
            ydpi = Inkscape::Util::Quantity::convert(height, "in", "px") / area.height();
            xdpi = ydpi;
            dpi = ydpi;
        }

        if (export_width != 0) {
            width = export_width;
            if ((width < 1) || (width > PNG_UINT_31_MAX)) {
                std::cerr << "InkFileExport::do_export_png: "
                          << "Export width " << width << " out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
                return;
            }
            xdpi = Inkscape::Util::Quantity::convert(width, "in", "px") / area.width();
            ydpi = export_height ? ydpi : xdpi;
            dpi = xdpi;
        }

        if (width == 0) {
            width = (unsigned long int) (Inkscape::Util::Quantity::convert(area.width(), "px", "in") * dpi + 0.5);
        }

        if (height == 0) {
            height = (unsigned long int) (Inkscape::Util::Quantity::convert(area.height(), "px", "in") * dpi + 0.5);
        }

        if ((width < 1) || (height < 1) || (width > PNG_UINT_31_MAX) || (height > PNG_UINT_31_MAX)) {
            std::cerr << "InkFileExport::do_export_png: Dimensions " << width << "x" << height << " are out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
            return;
        }

        // -------------------------- Bit Depth and Color Type --------------------

        int bit_depth = 8; // default of sp_export_png_file function
        int color_type = PNG_COLOR_TYPE_RGB_ALPHA; // default of sp_export_png_file function

        if (!export_png_color_mode.empty()) {
            // data as in ui/dialog/export.cpp:
            const std::map<std::string, std::pair<int, int>> color_modes = {
                {"Gray_1", {PNG_COLOR_TYPE_GRAY, 1}},
                {"Gray_2", {PNG_COLOR_TYPE_GRAY, 2}},
                {"Gray_4", {PNG_COLOR_TYPE_GRAY, 4}},
                {"Gray_8", {PNG_COLOR_TYPE_GRAY, 8}},
                {"Gray_16", {PNG_COLOR_TYPE_GRAY, 16}},
                {"RGB_8", {PNG_COLOR_TYPE_RGB, 8}},
                {"RGB_16", {PNG_COLOR_TYPE_RGB, 16}},
                {"GrayAlpha_8", {PNG_COLOR_TYPE_GRAY_ALPHA, 8}},
                {"GrayAlpha_16", {PNG_COLOR_TYPE_GRAY_ALPHA, 16}},
                {"RGBA_8", {PNG_COLOR_TYPE_RGB_ALPHA, 8}},
                {"RGBA_16", {PNG_COLOR_TYPE_RGB_ALPHA, 16}},
            };
            auto it = color_modes.find(export_png_color_mode);
            if (it == color_modes.end()) {
                std::cerr << "InkFileExport::do_export_png: "
                          << "Color mode " << export_png_color_mode.raw() << " 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;
                return;
            } else {
                std::tie(color_type, bit_depth) = it->second;
            }
        }

        guint32 bgcolor = get_bgcolor(doc);
        // ----------------------  Generate the PNG -------------------------------
#ifdef DEBUG
        std::cerr << "Background RRGGBBAA: " << std::hex << bgcolor << std::dec << std::endl;
        std::cerr << "Area "
                  << area[Geom::X][0] << ":" << area[Geom::Y][0] << ":"
                  << area[Geom::X][1] << ":" << area[Geom::Y][1] << " exported to "
                  << width << " x " << height << " pixels (" << dpi << " dpi)" << std::endl;
#endif

        if( sp_export_png_file(doc, filename_out.c_str(), area, width, height, xdpi, ydpi,
                               bgcolor, nullptr, nullptr, true, export_id_only ? items : std::vector<SPItem*>(),
                               false, color_type, bit_depth) == 1 ) {
        } else {
            std::cerr << "InkFileExport::do_export_png: Failed to export to " << filename_out << std::endl;
        }
}


/**
 *  Perform a PDF/PS/EPS export
 *
 *  \param doc Document to export.
 *  \param filename File to write to.
 *  \param mime MIME type to export as.
 */
int
InkFileExportCmd::do_export_ps_pdf(SPDocument* doc, std::string const &filename_in, std::string const & mime_type)
{
    // Check if we support mime type.
    Inkscape::Extension::DB::OutputList o;
    Inkscape::Extension::db.get_output_list(o);
    Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
    while (i != o.end() && strcmp( (*i)->get_mimetype(), mime_type.c_str() ) != 0) {
        ++i;
    }

    if (i == o.end()) {
        std::cerr << "InkFileExportCmd::do_export_ps_pdf: Could not find an extension to export to MIME type: " << mime_type << std::endl;
        return 1;
    }
    return do_export_ps_pdf(doc, filename_in, mime_type, *dynamic_cast<Inkscape::Extension::Output *>(*i));
}

/**
 *  Perform a PDF/PS/EPS export
 *
 *  \param doc Document to export.
 *  \param filename File to write to.
 *  \param mime MIME type to export as.
 *  \param Extension used for exporting
 */
int InkFileExportCmd::do_export_ps_pdf(SPDocument *doc, std::string const &filename_in, std::string const & mime_type,
                                       Inkscape::Extension::Output &extension)
{
    // check if the passed extension conforms to the mime type.
    assert(std::string(extension.get_mimetype()) == mime_type);
    // Start with options that are once per document.

    // Set export options.
    if (export_text_to_path) {
        extension.set_param_optiongroup("textToPath", "paths");
    } else if (export_latex) {
        extension.set_param_optiongroup("textToPath", "LaTeX");
    } else {
        extension.set_param_optiongroup("textToPath", "embed");
    }

    if (export_ignore_filters) {
        extension.set_param_bool("blurToBitmap", false);
    } else {
        extension.set_param_bool("blurToBitmap", true);

        gdouble dpi = 96.0;
        if (export_dpi) {
            dpi = export_dpi;
            if ((dpi < 1) || (dpi > 10000.0)) {
                g_warning("DPI value %lf out of range [1 - 10000]. Using 96 dpi instead.", export_dpi);
                dpi = 96;
            }
        }

        extension.set_param_int("resolution", (int)dpi);
    }

    // handle --export-pdf-version
    if (mime_type == "application/pdf") {
        bool set_export_pdf_version_fail = true;
        const gchar *pdfver_param_name = "PDFversion";
        if (!export_pdf_level.empty()) {
            // combine "PDF " and the given command line
            std::string version_gui_string = std::string("PDF-") + export_pdf_level.raw();
            try {
                // first, check if the given pdf version is selectable in the ComboBox
                if (extension.get_param_optiongroup_contains("PDFversion", version_gui_string.c_str())) {
                    extension.set_param_optiongroup(pdfver_param_name, version_gui_string.c_str());
                    set_export_pdf_version_fail = false;
                } else {
                    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\".",
                              export_pdf_level.c_str());
                }
            } catch (...) {
                // can be thrown along the way:
                // throw Extension::param_not_exist();
                // throw Extension::param_not_enum_param();
                g_warning("Parameter or Enum \"%s\" might not exist", pdfver_param_name);
            }
        }

        // set default pdf export version to 1.4, also if something went wrong
        if(set_export_pdf_version_fail) {
            extension.set_param_optiongroup(pdfver_param_name, "PDF-1.4");
        }
    }

    if (mime_type == "image/x-postscript" || mime_type == "image/x-e-postscript") {
        if ( export_ps_level < 2 || export_ps_level > 3 ) {
            g_warning("Only supported PostScript levels are 2 and 3."
                      " Defaulting to 2.");
            export_ps_level = 2;
        }

        extension.set_param_optiongroup("PSlevel", (export_ps_level == 3) ? "PS3" : "PS2");
    }

    return do_export_vector(doc, filename_in, extension);
}

/**
 *  Export a document using an export extension
 *
 *  \param doc Document to export.
 *  \param filename to export to.
 *  \param output extension used for export
 */
int InkFileExportCmd::do_export_extension(SPDocument *doc, std::string const &filename_in,
                                          Inkscape::Extension::Output *extension)
{
    std::string filename_out = get_filename_out(filename_in);
    if (extension) {
        extension->set_state(Inkscape::Extension::Extension::STATE_LOADED);
        try {
            extension->set_gui(false);
            extension->save(doc, filename_out.c_str());
        } catch (Inkscape::Extension::Output::save_failed const &) {

            std::cerr << __PRETTY_FUNCTION__ << ": Failed to save " << extension->get_id()
                      << " to: " << filename_out << std::endl;
            return 1;
        }
    }
    return 0;
}

std::string export_area_type_string(ExportAreaType type)
{
    switch (type) {
        case ExportAreaType::Area:
            return "--export-area";
        case ExportAreaType::Page:
            return "--export-area-page";
        case ExportAreaType::Drawing:
            return "--export-area-drawing";
        default:
            return "default";
    }
}

void InkFileExportCmd::set_export_area_type(ExportAreaType type)
{
    if ((export_area_type != ExportAreaType::Unset) && (export_area_type != type)) {
        std::cerr << "Warning: multiple export area types have been set, overriding "
                  << export_area_type_string(export_area_type) << " with " << export_area_type_string(type)
                  << std::endl;
    }
    export_area_type = type;
}

void InkFileExportCmd::set_export_area(const Glib::ustring &area)
{
    export_area = area;
    set_export_area_type(ExportAreaType::Area);
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
