1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * Print dialog.
5  */
6 /* Authors:
7  *   Kees Cook <kees@outflux.net>
8  *   Abhishek Sharma
9  *   Patrick McDermott
10  *
11  * Copyright (C) 2007 Kees Cook
12  * Copyright (C) 2017 Patrick McDermott
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include <cmath>
17 
18 #include <gtkmm.h>
19 
20 #include "inkscape.h"
21 #include "preferences.h"
22 #include "print.h"
23 
24 #include "extension/internal/cairo-render-context.h"
25 #include "extension/internal/cairo-renderer.h"
26 #include "document.h"
27 
28 #include "util/units.h"
29 #include "helper/png-write.h"
30 #include "svg/svg-color.h"
31 
32 #include <glibmm/i18n.h>
33 
34 
35 namespace Inkscape {
36 namespace UI {
37 namespace Dialog {
38 
Print(SPDocument * doc,SPItem * base)39 Print::Print(SPDocument *doc, SPItem *base) :
40     _doc (doc),
41     _base (base)
42 {
43     g_assert (_doc);
44     g_assert (_base);
45 
46     _printop = Gtk::PrintOperation::create();
47 
48     // set up dialog title, based on document name
49     const Glib::ustring jobname = _doc->getDocumentName() ? _doc->getDocumentName() : _("SVG Document");
50     Glib::ustring title = _("Print");
51     title += " ";
52     title += jobname;
53     _printop->set_job_name(title);
54 
55     _printop->set_unit(Gtk::UNIT_POINTS);
56     Glib::RefPtr<Gtk::PageSetup> page_setup = Gtk::PageSetup::create();
57 
58     // Default to a custom paper size, in case we can't find a more specific size
59     gdouble doc_width = _doc->getWidth().value("pt");
60     gdouble doc_height = _doc->getHeight().value("pt");
61     page_setup->set_paper_size(
62             Gtk::PaperSize("custom", "custom", doc_width, doc_height, Gtk::UNIT_POINTS));
63 
64     // Some print drivers, like the EPSON's ESC/P-R CUPS driver, don't accept custom
65     // page sizes, so we'll try to find a known page size.
66     // GTK+'s known paper sizes always have a longer height than width, so we'll rotate
67     // the page and set its orientation to landscape as necessary in order to match a paper size.
68     // Unfortunately, some printers, like Epilog laser cutters, don't understand landscape
69     // mode.
70     // As a compromise, we'll only rotate the page if we actually find a matching paper size,
71     // since laser cutter beds tend to be custom sizes.
72     Gtk::PageOrientation orientation = Gtk::PAGE_ORIENTATION_PORTRAIT;
73     if (_doc->getWidth().value("pt") > _doc->getHeight().value("pt")) {
74         orientation = Gtk::PAGE_ORIENTATION_REVERSE_LANDSCAPE;
75         std::swap(doc_width, doc_height);
76     }
77 
78     // attempt to match document size against known paper sizes
79     std::vector<Gtk::PaperSize> known_sizes = Gtk::PaperSize::get_paper_sizes(false);
80     for (auto& size : known_sizes) {
81         if (fabs(size.get_width(Gtk::UNIT_POINTS) - doc_width) >= 1.0) {
82             // width (short edge) doesn't match
83             continue;
84         }
85         if (fabs(size.get_height(Gtk::UNIT_POINTS) - doc_height) >= 1.0) {
86             // height (short edge) doesn't match
87             continue;
88         }
89         // size matches
90         page_setup->set_paper_size(size);
91         page_setup->set_orientation(orientation);
92         break;
93     }
94 
95     _printop->set_default_page_setup(page_setup);
96     _printop->set_use_full_page(true);
97 
98     // set up signals
99     _workaround._doc = _doc;
100     _workaround._base = _base;
101     _workaround._tab = &_tab;
102     _printop->signal_create_custom_widget().connect(sigc::mem_fun(*this, &Print::create_custom_widget));
103     _printop->signal_begin_print().connect(sigc::mem_fun(*this, &Print::begin_print));
104     _printop->signal_draw_page().connect(sigc::mem_fun(*this, &Print::draw_page));
105 
106     // build custom preferences tab
107     _printop->set_custom_tab_label(_("Rendering"));
108 }
109 
draw_page(const Glib::RefPtr<Gtk::PrintContext> & context,int)110 void Print::draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, int /*page_nr*/)
111 {
112     // TODO: If the user prints multiple copies we render the whole page for each copy
113     //       It would be more efficient to render the page once (e.g. in "begin_print")
114     //       and simply print this result as often as necessary
115 
116     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
117     //printf("%s %d\n",__FUNCTION__, page_nr);
118 
119     if (_workaround._tab->as_bitmap()) {
120         // Render as exported PNG
121         prefs->setBool("/dialogs/printing/asbitmap", true);
122         gdouble width = (_workaround._doc)->getWidth().value("px");
123         gdouble height = (_workaround._doc)->getHeight().value("px");
124         gdouble dpi = _workaround._tab->bitmap_dpi();
125         prefs->setDouble("/dialogs/printing/dpi", dpi);
126 
127         std::string tmp_png;
128         std::string tmp_base = "inkscape-print-png-XXXXXX";
129 
130         int tmp_fd;
131         if ( (tmp_fd = Glib::file_open_tmp(tmp_png, tmp_base)) >= 0) {
132             close(tmp_fd);
133 
134             guint32 bgcolor = 0x00000000;
135             Inkscape::XML::Node *nv = _workaround._doc->getReprNamedView();
136             if (nv && nv->attribute("pagecolor")){
137                 bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
138             }
139             if (nv && nv->attribute("inkscape:pageopacity")){
140                 double opacity = 1.0;
141                 sp_repr_get_double (nv, "inkscape:pageopacity", &opacity);
142                 bgcolor |= SP_COLOR_F_TO_U(opacity);
143             }
144 
145             sp_export_png_file(_workaround._doc, tmp_png.c_str(), 0.0, 0.0,
146                 width, height,
147                 (unsigned long)(Inkscape::Util::Quantity::convert(width, "px", "in") * dpi),
148                 (unsigned long)(Inkscape::Util::Quantity::convert(height, "px", "in") * dpi),
149                 dpi, dpi, bgcolor, nullptr, nullptr, true, std::vector<SPItem*>());
150 
151             // This doesn't seem to work:
152             //context->set_cairo_context ( Cairo::Context::create (Cairo::ImageSurface::create_from_png (tmp_png) ), dpi, dpi );
153             //
154             // so we'll use a surface pattern blat instead...
155             //
156             // but the C++ interface isn't implemented in cairomm:
157             //context->get_cairo_context ()->set_source_surface(Cairo::ImageSurface::create_from_png (tmp_png) );
158             //
159             // so do it in C:
160             {
161                 auto png = Cairo::ImageSurface::create_from_png(tmp_png);
162                 auto pattern = Cairo::SurfacePattern::create(png);
163                 auto cr = context->get_cairo_context();
164                 auto m = cr->get_matrix();
165                 cr->scale(Inkscape::Util::Quantity::convert(1, "in", "pt") / dpi,
166                           Inkscape::Util::Quantity::convert(1, "in", "pt") / dpi);
167                 // FIXME: why is the origin offset??
168                 cr->set_source(pattern);
169                 cr->paint();
170                 cr->set_matrix(m);
171             }
172 
173             // Clean up
174             unlink (tmp_png.c_str());
175         }
176         else {
177             g_warning("%s", _("Could not open temporary PNG for bitmap printing"));
178         }
179     }
180     else {
181         // Render as vectors
182         prefs->setBool("/dialogs/printing/asbitmap", false);
183         Inkscape::Extension::Internal::CairoRenderer renderer;
184         Inkscape::Extension::Internal::CairoRenderContext *ctx = renderer.createContext();
185 
186         // ctx->setPSLevel(CAIRO_PS_LEVEL_3);
187         ctx->setTextToPath(false);
188         ctx->setFilterToBitmap(true);
189         ctx->setBitmapResolution(72);
190 
191         auto cr = context->get_cairo_context();
192         auto surface = cr->get_target();
193         auto ctm = cr->get_matrix();
194 
195         bool ret = ctx->setSurfaceTarget(surface->cobj(), true, &ctm);
196         if (ret) {
197             ret = renderer.setupDocument (ctx, _workaround._doc, TRUE, 0., nullptr);
198             if (ret) {
199                 renderer.renderItem(ctx, _workaround._base);
200                 ctx->finish(false);  // do not finish the cairo_surface_t - it's owned by our GtkPrintContext!
201             }
202             else {
203                 g_warning("%s", _("Could not set up Document"));
204             }
205         }
206         else {
207             g_warning("%s", _("Failed to set CairoRenderContext"));
208         }
209 
210         // Clean up
211         renderer.destroyContext(ctx);
212     }
213 
214 }
215 
create_custom_widget()216 Gtk::Widget *Print::create_custom_widget()
217 {
218     //printf("%s\n",__FUNCTION__);
219     return &_tab;
220 }
221 
begin_print(const Glib::RefPtr<Gtk::PrintContext> &)222 void Print::begin_print(const Glib::RefPtr<Gtk::PrintContext>&)
223 {
224     //printf("%s\n",__FUNCTION__);
225     _printop->set_n_pages(1);
226 }
227 
run(Gtk::PrintOperationAction,Gtk::Window & parent_window)228 Gtk::PrintOperationResult Print::run(Gtk::PrintOperationAction, Gtk::Window &parent_window)
229 {
230     // Remember to restore the previous print settings
231     _printop->set_print_settings(SP_ACTIVE_DESKTOP->printer_settings._gtk_print_settings);
232 
233     try {
234         Gtk::PrintOperationResult res = _printop->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, parent_window);
235 
236         // Save printer settings (but only on success)
237         if (res == Gtk::PRINT_OPERATION_RESULT_APPLY) {
238             SP_ACTIVE_DESKTOP->printer_settings._gtk_print_settings = _printop->get_print_settings();
239         }
240 
241         return res;
242     } catch (const Glib::Error &e) {
243         g_warning("Failed to print '%s': %s", _doc->getDocumentName(), e.what().c_str());
244     }
245 
246     return Gtk::PRINT_OPERATION_RESULT_ERROR;
247 }
248 
249 
250 } // namespace Dialog
251 } // namespace UI
252 } // namespace Inkscape
253 
254 /*
255   Local Variables:
256   mode:c++
257   c-file-style:"stroustrup"
258   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
259   indent-tabs-mode:nil
260   fill-column:99
261   End:
262 */
263 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
264