// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Print dialog. */ /* Authors: * Kees Cook * Abhishek Sharma * Patrick McDermott * * Copyright (C) 2007 Kees Cook * Copyright (C) 2017 Patrick McDermott * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include "inkscape.h" #include "preferences.h" #include "print.h" #include "extension/internal/cairo-render-context.h" #include "extension/internal/cairo-renderer.h" #include "document.h" #include "util/units.h" #include "helper/png-write.h" #include "svg/svg-color.h" #include namespace Inkscape { namespace UI { namespace Dialog { Print::Print(SPDocument *doc, SPItem *base) : _doc (doc), _base (base) { g_assert (_doc); g_assert (_base); _printop = Gtk::PrintOperation::create(); // set up dialog title, based on document name const Glib::ustring jobname = _doc->getDocumentName() ? _doc->getDocumentName() : _("SVG Document"); Glib::ustring title = _("Print"); title += " "; title += jobname; _printop->set_job_name(title); _printop->set_unit(Gtk::UNIT_POINTS); Glib::RefPtr page_setup = Gtk::PageSetup::create(); // Default to a custom paper size, in case we can't find a more specific size gdouble doc_width = _doc->getWidth().value("pt"); gdouble doc_height = _doc->getHeight().value("pt"); page_setup->set_paper_size( Gtk::PaperSize("custom", "custom", doc_width, doc_height, Gtk::UNIT_POINTS)); // Some print drivers, like the EPSON's ESC/P-R CUPS driver, don't accept custom // page sizes, so we'll try to find a known page size. // GTK+'s known paper sizes always have a longer height than width, so we'll rotate // the page and set its orientation to landscape as necessary in order to match a paper size. // Unfortunately, some printers, like Epilog laser cutters, don't understand landscape // mode. // As a compromise, we'll only rotate the page if we actually find a matching paper size, // since laser cutter beds tend to be custom sizes. Gtk::PageOrientation orientation = Gtk::PAGE_ORIENTATION_PORTRAIT; if (_doc->getWidth().value("pt") > _doc->getHeight().value("pt")) { orientation = Gtk::PAGE_ORIENTATION_REVERSE_LANDSCAPE; std::swap(doc_width, doc_height); } // attempt to match document size against known paper sizes std::vector known_sizes = Gtk::PaperSize::get_paper_sizes(false); for (auto& size : known_sizes) { if (fabs(size.get_width(Gtk::UNIT_POINTS) - doc_width) >= 1.0) { // width (short edge) doesn't match continue; } if (fabs(size.get_height(Gtk::UNIT_POINTS) - doc_height) >= 1.0) { // height (short edge) doesn't match continue; } // size matches page_setup->set_paper_size(size); page_setup->set_orientation(orientation); break; } _printop->set_default_page_setup(page_setup); _printop->set_use_full_page(true); // set up signals _workaround._doc = _doc; _workaround._base = _base; _workaround._tab = &_tab; _printop->signal_create_custom_widget().connect(sigc::mem_fun(*this, &Print::create_custom_widget)); _printop->signal_begin_print().connect(sigc::mem_fun(*this, &Print::begin_print)); _printop->signal_draw_page().connect(sigc::mem_fun(*this, &Print::draw_page)); // build custom preferences tab _printop->set_custom_tab_label(_("Rendering")); } void Print::draw_page(const Glib::RefPtr& context, int /*page_nr*/) { // TODO: If the user prints multiple copies we render the whole page for each copy // It would be more efficient to render the page once (e.g. in "begin_print") // and simply print this result as often as necessary Inkscape::Preferences *prefs = Inkscape::Preferences::get(); //printf("%s %d\n",__FUNCTION__, page_nr); if (_workaround._tab->as_bitmap()) { // Render as exported PNG prefs->setBool("/dialogs/printing/asbitmap", true); gdouble width = (_workaround._doc)->getWidth().value("px"); gdouble height = (_workaround._doc)->getHeight().value("px"); gdouble dpi = _workaround._tab->bitmap_dpi(); prefs->setDouble("/dialogs/printing/dpi", dpi); std::string tmp_png; std::string tmp_base = "inkscape-print-png-XXXXXX"; int tmp_fd; if ( (tmp_fd = Glib::file_open_tmp(tmp_png, tmp_base)) >= 0) { close(tmp_fd); guint32 bgcolor = 0x00000000; Inkscape::XML::Node *nv = _workaround._doc->getReprNamedView(); if (nv && nv->attribute("pagecolor")){ bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); } if (nv && nv->attribute("inkscape:pageopacity")){ double opacity = nv->getAttributeDouble("inkscape:pageopacity", 1.0); bgcolor |= SP_COLOR_F_TO_U(opacity); } sp_export_png_file(_workaround._doc, tmp_png.c_str(), 0.0, 0.0, width, height, (unsigned long)(Inkscape::Util::Quantity::convert(width, "px", "in") * dpi), (unsigned long)(Inkscape::Util::Quantity::convert(height, "px", "in") * dpi), dpi, dpi, bgcolor, nullptr, nullptr, true, std::vector()); // This doesn't seem to work: //context->set_cairo_context ( Cairo::Context::create (Cairo::ImageSurface::create_from_png (tmp_png) ), dpi, dpi ); // // so we'll use a surface pattern blat instead... // // but the C++ interface isn't implemented in cairomm: //context->get_cairo_context ()->set_source_surface(Cairo::ImageSurface::create_from_png (tmp_png) ); // // so do it in C: { auto png = Cairo::ImageSurface::create_from_png(tmp_png); auto pattern = Cairo::SurfacePattern::create(png); auto cr = context->get_cairo_context(); auto m = cr->get_matrix(); cr->scale(Inkscape::Util::Quantity::convert(1, "in", "pt") / dpi, Inkscape::Util::Quantity::convert(1, "in", "pt") / dpi); // FIXME: why is the origin offset?? cr->set_source(pattern); cr->paint(); cr->set_matrix(m); } // Clean up unlink (tmp_png.c_str()); } else { g_warning("%s", _("Could not open temporary PNG for bitmap printing")); } } else { // Render as vectors prefs->setBool("/dialogs/printing/asbitmap", false); Inkscape::Extension::Internal::CairoRenderer renderer; Inkscape::Extension::Internal::CairoRenderContext *ctx = renderer.createContext(); // ctx->setPSLevel(CAIRO_PS_LEVEL_3); ctx->setTextToPath(false); ctx->setFilterToBitmap(true); ctx->setBitmapResolution(72); auto cr = context->get_cairo_context(); auto surface = cr->get_target(); auto ctm = cr->get_matrix(); bool ret = ctx->setSurfaceTarget(surface->cobj(), true, &ctm); if (ret) { ret = renderer.setupDocument (ctx, _workaround._doc, TRUE, 0., nullptr); if (ret) { renderer.renderItem(ctx, _workaround._base); ctx->finish(false); // do not finish the cairo_surface_t - it's owned by our GtkPrintContext! } else { g_warning("%s", _("Could not set up Document")); } } else { g_warning("%s", _("Failed to set CairoRenderContext")); } // Clean up renderer.destroyContext(ctx); } } Gtk::Widget *Print::create_custom_widget() { //printf("%s\n",__FUNCTION__); return &_tab; } void Print::begin_print(const Glib::RefPtr&) { //printf("%s\n",__FUNCTION__); _printop->set_n_pages(1); } Gtk::PrintOperationResult Print::run(Gtk::PrintOperationAction, Gtk::Window &parent_window) { // Remember to restore the previous print settings _printop->set_print_settings(SP_ACTIVE_DESKTOP->printer_settings._gtk_print_settings); try { Gtk::PrintOperationResult res = _printop->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, parent_window); // Save printer settings (but only on success) if (res == Gtk::PRINT_OPERATION_RESULT_APPLY) { SP_ACTIVE_DESKTOP->printer_settings._gtk_print_settings = _printop->get_print_settings(); } return res; } catch (const Glib::Error &e) { g_warning("Failed to print '%s': %s", _doc->getDocumentName(), e.what().c_str()); } return Gtk::PRINT_OPERATION_RESULT_ERROR; } } // namespace Dialog } // namespace UI } // namespace Inkscape /* 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:fileencoding=utf-8:textwidth=99 :