// 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 "object/sp-page.h" #include "util/units.h" #include "helper/png-write.h" #include "page-manager.h" #include "svg/svg-color.h" #include namespace Inkscape { namespace UI { namespace Dialog { Glib::RefPtr &get_printer_settings() { static Glib::RefPtr printer_settings; return printer_settings; } 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 set_paper_size(page_setup, _doc->getWidth().value("pt"), _doc->getHeight().value("pt")); _printop->set_default_page_setup(page_setup); _printop->set_use_full_page(true); _printop->set_n_pages(1); // Now process actual multi-page setup. auto &pm = _doc->getPageManager(); if (pm.hasPages()) { // This appears to be limiting which pages get rendered _printop->set_n_pages(pm.getPageCount()); _printop->set_current_page(pm.getSelectedPageIndex()); _printop->signal_request_page_setup().connect(sigc::mem_fun(*this, &Print::setup_page)); } // 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")); } /** * Return the required page setup, only connected for multi-page documents * and only required where there are pages of different sizes. */ void Print::setup_page(const Glib::RefPtr& context, int page_nr, const Glib::RefPtr &setup) { auto &pm = _workaround._doc->getPageManager(); if (auto page = pm.getPage(page_nr)) { auto rect = page->getDesktopRect(); auto width = Inkscape::Util::Quantity::convert(rect.width(), "px", "pt"); auto height = Inkscape::Util::Quantity::convert(rect.height(), "px", "pt"); set_paper_size(setup, width, height); } } /** * Set the paper size with correct orientation. */ void Print::set_paper_size(const Glib::RefPtr &page_setup, double page_width, double page_height) { auto p_size = Gtk::PaperSize("custom", "custom", page_width, page_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 (page_width > page_height) { orientation = Gtk::PAGE_ORIENTATION_REVERSE_LANDSCAPE; std::swap(page_width, page_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) - page_width) >= 1.0) { // width (short edge) doesn't match continue; } if (fabs(size.get_height(Gtk::UNIT_POINTS) - page_height) >= 1.0) { // height (short edge) doesn't match continue; } // size matches p_size = size; break; } page_setup->set_paper_size(p_size); page_setup->set_orientation(orientation); } 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); auto &pm = _workaround._doc->getPageManager(); auto page = pm.getPage(page_nr); // nullptr when no pages. if (_workaround._tab->as_bitmap()) { // Render as exported PNG prefs->setBool("/dialogs/printing/asbitmap", true); gdouble dpi = _workaround._tab->bitmap_dpi(); prefs->setDouble("/dialogs/printing/dpi", dpi); auto rect = *(_workaround._doc->preferredBounds()); if (page) { rect = page->getDesktopRect(); } 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(), rect, (unsigned long)(Inkscape::Util::Quantity::convert(rect.width(), "px", "in") * dpi), (unsigned long)(Inkscape::Util::Quantity::convert(rect.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); if (ret) { if (auto page = pm.getPage(page_nr)) { renderer.renderPage(ctx, _workaround._doc, page, false); } else { 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() { return &_tab; } void Print::begin_print(const Glib::RefPtr&) { // Could change which pages get printed here, but nothing to do. } Gtk::PrintOperationResult Print::run(Gtk::PrintOperationAction, Gtk::Window &parent_window) { // Remember to restore the previous print settings _printop->set_print_settings(get_printer_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) { get_printer_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 :