1 /*
2  * gnote
3  *
4  * Copyright (C) 2010-2013,2015-2017,2019-2021 Aurimas Cernius
5  * Copyright (C) 2009 Hubert Figuiere
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 
23 #include <glibmm/i18n.h>
24 #include <glibmm/miscutils.h>
25 #include <gtkmm/image.h>
26 #include <gtkmm/printoperation.h>
27 
28 #include "debug.hpp"
29 #include "iactionmanager.hpp"
30 #include "notetag.hpp"
31 #include "notewindow.hpp"
32 #include "printnotesnoteaddin.hpp"
33 #include "utils.hpp"
34 
35 namespace printnotes {
36 
PrintNotesModule()37   PrintNotesModule::PrintNotesModule()
38   {
39     ADD_INTERFACE_IMPL(PrintNotesNoteAddin);
40   }
41 
initialize()42   void PrintNotesNoteAddin::initialize()
43   {
44   }
45 
46 
shutdown()47   void PrintNotesNoteAddin::shutdown()
48   {
49   }
50 
51 
on_note_opened()52   void PrintNotesNoteAddin::on_note_opened()
53   {
54     register_main_window_action_callback("printnotes-print",
55       sigc::mem_fun(*this, &PrintNotesNoteAddin::print_button_clicked));
56   }
57 
58 
get_actions_popover_widgets() const59   std::vector<gnote::PopoverWidget> PrintNotesNoteAddin::get_actions_popover_widgets() const
60   {
61     auto widgets = NoteAddin::get_actions_popover_widgets();
62     auto button = gnote::utils::create_popover_button("win.printnotes-print", _("Print…"));
63     widgets.push_back(gnote::PopoverWidget::create_for_note(gnote::PRINT_ORDER, button));
64     return widgets;
65   }
66 
67 
print_button_clicked(const Glib::VariantBase &)68   void PrintNotesNoteAddin::print_button_clicked(const Glib::VariantBase&)
69   {
70     try {
71       m_print_op = Gtk::PrintOperation::create();
72       m_print_op->set_job_name(get_note()->get_title());
73 
74       Glib::RefPtr<Gtk::PrintSettings> settings = Gtk::PrintSettings::create();
75 
76       Glib::ustring dir = Glib::get_user_special_dir(Glib::USER_DIRECTORY_DOCUMENTS);
77       if (dir.empty()) {
78         dir = Glib::get_home_dir();
79       }
80       Glib::ustring ext;
81       if (settings->get(Gtk::PrintSettings::Keys::OUTPUT_FILE_FORMAT) == "ps") {
82         ext = ".ps";
83       }
84       else {
85         ext = ".pdf";
86       }
87 
88       Glib::ustring uri = "file://";
89       uri += dir + "/gnotes" + ext;
90       settings->set (Gtk::PrintSettings::Keys::OUTPUT_URI, uri);
91       m_print_op->set_print_settings (settings);
92 
93       m_print_op->signal_begin_print().connect(
94         sigc::mem_fun(*this, &PrintNotesNoteAddin::on_begin_print));
95       m_print_op->signal_draw_page().connect(
96         sigc::mem_fun(*this, &PrintNotesNoteAddin::on_draw_page));
97       m_print_op->signal_end_print().connect(
98         sigc::mem_fun(*this, &PrintNotesNoteAddin::on_end_print));
99 
100       m_print_op->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *get_host_window());
101     }
102     catch (const sharp::Exception & e)
103     {
104       DBG_OUT("Exception while printing %s: %s", get_note()->get_title().c_str(),
105               e.what());
106       gnote::utils::HIGMessageDialog dlg(get_host_window(),
107                                          GTK_DIALOG_MODAL,
108                                          Gtk::MESSAGE_ERROR,
109                                          Gtk::BUTTONS_OK,
110                                          _("Error printing note"),
111                                          e.what());
112       dlg.run ();
113     }
114     m_print_op.reset();
115   }
116 
117 
get_paragraph_attributes(const Glib::RefPtr<Pango::Layout> & layout,double dpiX,int & indentation,Gtk::TextIter & position,const Gtk::TextIter & limit)118   std::vector<Pango::Attribute> PrintNotesNoteAddin::get_paragraph_attributes(const Glib::RefPtr<Pango::Layout> & layout,
119                                                      double dpiX,
120                                                      int & indentation,
121                                                      Gtk::TextIter & position,
122                                                      const Gtk::TextIter & limit)
123   {
124     std::vector<Pango::Attribute> attributes;
125     indentation = 0;
126 
127     Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tags = position.get_tags();
128     position.forward_to_tag_toggle(Glib::RefPtr<Gtk::TextTag>(NULL));
129     if (position.compare (limit) > 0) {
130       position = limit;
131     }
132 
133     Glib::RefPtr<Gdk::Screen> screen = get_window()->get_screen();
134     double screen_dpiX = screen->get_width_mm() * 254 / screen->get_width();
135 
136     for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator iter = tags.begin();
137         iter != tags.end(); ++iter) {
138 
139       Glib::RefPtr<Gtk::TextTag> tag(*iter);
140 
141       if (tag->property_paragraph_background_set()) {
142         Gdk::RGBA color = tag->property_paragraph_background_rgba();
143         attributes.push_back(Pango::Attribute::create_attr_background(
144                                color.get_red_u(), color.get_green_u(),
145                                color.get_blue_u()));
146       }
147       if (tag->property_foreground_set()) {
148         Gdk::RGBA color = tag->property_foreground_rgba();
149         attributes.push_back(Pango::Attribute::create_attr_foreground(
150                                color.get_red_u(), color.get_green_u(),
151                                color.get_blue_u()));
152       }
153       if (tag->property_indent_set()) {
154         layout->set_indent(tag->property_indent());
155       }
156       if (tag->property_left_margin_set()) {
157         indentation = (int)(tag->property_left_margin() / screen_dpiX * dpiX);
158       }
159       if (tag->property_right_margin_set()) {
160         indentation = (int)(tag->property_right_margin() / screen_dpiX * dpiX);
161       }
162 //      if (tag->property_font_desc()) {
163       attributes.push_back(
164         Pango::Attribute::create_attr_font_desc (tag->property_font_desc()));
165 //      }
166       if (tag->property_family_set()) {
167         attributes.push_back(
168           Pango::Attribute::create_attr_family (tag->property_family()));
169       }
170       if (tag->property_size_set()) {
171         attributes.push_back(Pango::Attribute::create_attr_size (
172                                tag->property_size()));
173       }
174       if (tag->property_style_set()) {
175         attributes.push_back(Pango::Attribute::create_attr_style (
176                                tag->property_style()));
177       }
178       if (tag->property_underline_set()
179           && tag->property_underline() != Pango::UNDERLINE_ERROR) {
180         attributes.push_back(
181           Pango::Attribute::create_attr_underline (
182             tag->property_underline()));
183       }
184       if (tag->property_weight_set()) {
185         attributes.push_back(
186           Pango::Attribute::create_attr_weight(
187             Pango::Weight(tag->property_weight().get_value())));
188       }
189       if (tag->property_strikethrough_set()) {
190         attributes.push_back(
191           Pango::Attribute::create_attr_strikethrough (
192             tag->property_strikethrough()));
193       }
194       if (tag->property_rise_set()) {
195         attributes.push_back(Pango::Attribute::create_attr_rise (
196                                tag->property_rise()));
197       }
198       if (tag->property_scale_set()) {
199         attributes.push_back(Pango::Attribute::create_attr_scale (
200                                tag->property_scale()));
201       }
202       if (tag->property_stretch_set()) {
203         attributes.push_back(Pango::Attribute::create_attr_stretch (
204                                tag->property_stretch()));
205       }
206     }
207 
208     return attributes;
209   }
210 
211   Glib::RefPtr<Pango::Layout>
create_layout_for_paragraph(const Glib::RefPtr<Gtk::PrintContext> & context,Gtk::TextIter p_start,Gtk::TextIter p_end,int & indentation)212   PrintNotesNoteAddin::create_layout_for_paragraph(const Glib::RefPtr<Gtk::PrintContext> & context,
213                                                    Gtk::TextIter p_start,
214                                                    Gtk::TextIter p_end,
215                                                    int & indentation)
216   {
217     Glib::RefPtr<Pango::Layout> layout = context->create_pango_layout();
218 
219     layout->set_font_description(
220       get_window()->editor()->get_pango_context()->get_font_description());
221     int start_index = p_start.get_line_index();
222     indentation = 0;
223 
224     double dpiX = context->get_dpi_x();
225     {
226       Pango::AttrList attr_list;
227 
228       Gtk::TextIter segm_start = p_start;
229       Gtk::TextIter segm_end;
230 
231       while (segm_start.compare (p_end) < 0) {
232         segm_end = segm_start;
233         auto attrs = get_paragraph_attributes(layout, dpiX, indentation, segm_end, p_end);
234 
235         guint si = (guint) (segm_start.get_line_index() - start_index);
236         guint ei = (guint) (segm_end.get_line_index() - start_index);
237 
238         for(auto & a : attrs) {
239           a.set_start_index(si);
240           a.set_end_index(ei);
241           attr_list.insert(a);
242         }
243         segm_start = segm_end;
244       }
245 
246       layout->set_attributes(attr_list);
247     }
248 
249     gnote::DepthNoteTag::Ptr depth = get_buffer()->find_depth_tag(p_start);
250     if(depth) {
251         indentation += ((int) (dpiX / 3)) * depth->get_depth();
252     }
253     layout->set_width(pango_units_from_double((int)context->get_width() -
254                                               m_margin_left - m_margin_right - indentation));
255     layout->set_wrap (Pango::WRAP_WORD_CHAR);
256     layout->set_text (get_buffer()->get_slice (p_start, p_end, false));
257     return layout;
258   }
259 
260 
261   Glib::RefPtr<Pango::Layout>
create_layout_for_pagenumbers(const Glib::RefPtr<Gtk::PrintContext> & context,int page_number,int total_pages)262   PrintNotesNoteAddin::create_layout_for_pagenumbers(const Glib::RefPtr<Gtk::PrintContext> & context,
263                                 int page_number, int total_pages)
264   {
265     Glib::RefPtr<Pango::Layout> layout = context->create_pango_layout();
266     Pango::FontDescription font_desc = get_window()->editor()->get_pango_context()->get_font_description();
267     font_desc.set_style(Pango::STYLE_NORMAL);
268     font_desc.set_weight(Pango::WEIGHT_LIGHT);
269     layout->set_font_description(font_desc);
270     layout->set_width(pango_units_from_double((int)context->get_width()));
271 
272     // %1 is the page number, %2 is the total number of pages
273     Glib::ustring footer_left = Glib::ustring::compose(_("Page %1 of %2"),
274                                   page_number, total_pages);
275     layout->set_alignment(Pango::ALIGN_LEFT);
276     layout->set_text (footer_left);
277 
278     return layout;
279   }
280 
281 
282   Glib::RefPtr<Pango::Layout>
create_layout_for_timestamp(const Glib::RefPtr<Gtk::PrintContext> & context)283   PrintNotesNoteAddin::create_layout_for_timestamp(const Glib::RefPtr<Gtk::PrintContext> & context)
284   {
285     Glib::ustring timestamp = sharp::date_time_to_string(Glib::DateTime::create_now_local(), "%c");
286 
287     Glib::RefPtr<Pango::Layout> layout = context->create_pango_layout ();
288     Pango::FontDescription font_desc = get_window()->editor()->get_pango_context()->get_font_description();
289     font_desc.set_style(Pango::STYLE_NORMAL);
290     font_desc.set_weight(Pango::WEIGHT_LIGHT);
291     layout->set_font_description(font_desc);
292     layout->set_width(pango_units_from_double((int) context->get_width()));
293 
294     layout->set_alignment(Pango::ALIGN_RIGHT);
295     layout->set_text (timestamp);
296 
297     return layout;
298   }
299 
compute_footer_height(const Glib::RefPtr<Gtk::PrintContext> & context)300   int PrintNotesNoteAddin::compute_footer_height(const Glib::RefPtr<Gtk::PrintContext> & context)
301   {
302     Glib::RefPtr<Pango::Layout> layout = create_layout_for_timestamp(context);
303     Pango::Rectangle ink_rect;
304     Pango::Rectangle logical_rect;
305     layout->get_extents(ink_rect, logical_rect);
306     return pango_units_to_double(ink_rect.get_height())
307       + cm_to_pixel(0.5, context->get_dpi_y());
308   }
309 
310 
on_begin_print(const Glib::RefPtr<Gtk::PrintContext> & context)311   void PrintNotesNoteAddin::on_begin_print(const Glib::RefPtr<Gtk::PrintContext>& context)
312   {
313     m_timestamp_footer = create_layout_for_timestamp(context);
314     // Create and initialize the page margins
315     m_margin_top = cm_to_pixel (1.5, context->get_dpi_y());
316     m_margin_left = cm_to_pixel (1, context->get_dpi_x());
317     m_margin_right = cm_to_pixel (1, context->get_dpi_x());
318     m_margin_bottom = 0;
319     double max_height = pango_units_from_double(context->get_height()
320                                                 - m_margin_top - m_margin_bottom
321                                                 - compute_footer_height(context));
322 
323     DBG_OUT("margins = %d %d %d %d", m_margin_top, m_margin_left,
324             m_margin_right, m_margin_bottom);
325 
326     m_page_breaks.clear();
327 
328     Gtk::TextIter position;
329     Gtk::TextIter end_iter;
330     get_buffer()->get_bounds (position, end_iter);
331 
332     double page_height = 0;
333     bool done = position.compare (end_iter) >= 0;
334     while (!done) {
335       Gtk::TextIter line_end = position;
336       if (!line_end.ends_line ()) {
337         line_end.forward_to_line_end ();
338       }
339 
340       int paragraph_number = position.get_line();
341       int indentation = 0;
342       Glib::RefPtr<Pango::Layout> layout = create_layout_for_paragraph(
343         context, position, line_end, indentation);
344 
345       Pango::Rectangle ink_rect;
346       Pango::Rectangle logical_rect;
347       for(int line_in_paragraph = 0;  line_in_paragraph < layout->get_line_count();
348           line_in_paragraph++) {
349         Glib::RefPtr<Pango::LayoutLine> line = layout->get_line(line_in_paragraph);
350         line->get_extents (ink_rect, logical_rect);
351 
352         if ((page_height + logical_rect.get_height()) >= max_height) {
353           PageBreak(paragraph_number, line_in_paragraph);
354           m_page_breaks.push_back (PageBreak(paragraph_number, line_in_paragraph));
355           page_height = 0;
356         }
357 
358         page_height += logical_rect.get_height();
359 
360       }
361       position.forward_line ();
362       done = position.compare (end_iter) >= 0;
363     }
364 
365     m_print_op->set_n_pages(m_page_breaks.size() + 1);
366   }
367 
368 
369 
on_draw_page(const Glib::RefPtr<Gtk::PrintContext> & context,guint page_nr)370   void PrintNotesNoteAddin::on_draw_page(const Glib::RefPtr<Gtk::PrintContext>& context, guint page_nr)
371   {
372     Cairo::RefPtr<Cairo::Context> cr = context->get_cairo_context();
373     cr->move_to (m_margin_left, m_margin_top);
374 
375     PageBreak start;
376     if (page_nr != 0) {
377       start = m_page_breaks [page_nr - 1];
378     }
379 
380     PageBreak end(-1, -1);
381     if (m_page_breaks.size() > page_nr) {
382       end = m_page_breaks [page_nr];
383     }
384 
385     Gtk::TextIter position;
386     Gtk::TextIter end_iter;
387     get_buffer()->get_bounds (position, end_iter);
388 
389     // Fast-forward to the starting line
390     while (position.get_line() < start.get_paragraph()) {
391       position.forward_line ();
392     }
393 
394     bool done = position.compare (end_iter) >= 0;
395     while (!done) {
396       Gtk::TextIter line_end = position;
397       if (!line_end.ends_line ()) {
398         line_end.forward_to_line_end ();
399       }
400 
401 
402       int paragraph_number = position.get_line();
403       int indentation;
404 
405       {
406         Glib::RefPtr<Pango::Layout> layout =
407           create_layout_for_paragraph (context,position, line_end, indentation);
408 
409         for(int line_number = 0;
410             line_number < layout->get_line_count() && !done;
411             line_number++) {
412           // Skip the lines up to the starting line in the
413           // first paragraph on this page
414           if ((paragraph_number == start.get_paragraph()) &&
415               (line_number < start.get_line())) {
416             continue;
417           }
418           // Break as soon as we hit the end line
419           if ((paragraph_number == end.get_paragraph()) &&
420               (line_number == end.get_line())) {
421             done = true;
422             break;
423           }
424 
425 
426 
427           Glib::RefPtr<Pango::LayoutLine> line = layout->get_line(line_number);
428 
429           Pango::Rectangle ink_rect;
430           Pango::Rectangle logical_rect;
431           line->get_extents (ink_rect, logical_rect);
432 
433           double curX, curY;
434           cr->get_current_point(curX, curY);
435           cr->move_to (m_margin_left + indentation, curY);
436           int line_height = pango_units_to_double(logical_rect.get_height());
437 
438           double x, y;
439           x = m_margin_left + indentation;
440           cr->get_current_point(curX, curY);
441           y = curY + line_height;
442           pango_cairo_show_layout_line(cr->cobj(), line->gobj());
443           cr->move_to(x, y);
444         }
445       }
446 
447       position.forward_line ();
448       done = done || (position.compare (end_iter) >= 0);
449     }
450 
451     // Print the footer
452     int total_height = context->get_height();
453     int total_width = context->get_width();
454     int footer_height = 0;
455 
456     double footer_anchor_x, footer_anchor_y;
457 
458     {
459       Glib::RefPtr<Pango::Layout> pages_footer
460         = create_layout_for_pagenumbers (context, page_nr + 1,
461                                          m_page_breaks.size() + 1);
462 
463       Pango::Rectangle ink_footer_rect;
464       Pango::Rectangle logical_footer_rect;
465       pages_footer->get_extents(ink_footer_rect, logical_footer_rect);
466 
467       footer_anchor_x = cm_to_pixel(0.5, context->get_dpi_x());
468       footer_anchor_y = total_height - m_margin_bottom;
469       footer_height = pango_units_to_double(logical_footer_rect.get_height());
470 
471       cr->move_to(total_width - pango_units_to_double(logical_footer_rect.get_width()) - cm_to_pixel(0.5, context->get_dpi_x()), footer_anchor_y);
472 
473       pango_cairo_show_layout_line(cr->cobj(),
474                                    (pages_footer->get_line(0))->gobj());
475 
476     }
477 
478     cr->move_to(footer_anchor_x, footer_anchor_y);
479     pango_cairo_show_layout_line(cr->cobj(),
480                                  (m_timestamp_footer->get_line(0))->gobj());
481 
482     cr->move_to(cm_to_pixel(0.5, context->get_dpi_x()),
483                 total_height - m_margin_bottom - footer_height);
484     cr->line_to(total_width - cm_to_pixel(0.5, context->get_dpi_x()),
485                 total_height - m_margin_bottom - footer_height);
486     cr->stroke();
487   }
488 
489 
on_end_print(const Glib::RefPtr<Gtk::PrintContext> &)490   void PrintNotesNoteAddin::on_end_print(const Glib::RefPtr<Gtk::PrintContext>&)
491   {
492     m_page_breaks.clear ();
493     m_timestamp_footer.reset();
494   }
495 
496 }
497