diff --git a/src/data/settings.cpp b/src/data/settings.cpp index 76da19ce..0e41b3e8 100644 --- a/src/data/settings.cpp +++ b/src/data/settings.cpp @@ -151,7 +151,15 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(StyleSheetSettings) { REFLECT(card_draw_editing); REFLECT(card_normal_export); REFLECT(card_spellcheck_enabled); -} +} + +// ----------------------------------------------------------------------------- : Printing settings + +IMPLEMENT_REFLECTION_ENUM(CutterLinesType) { + VALUE_N("all", CUTTER_ALL); + VALUE_N("no intersect", CUTTER_NO_INTERSECTION); + VALUE_N("none", CUTTER_NONE); +} // ----------------------------------------------------------------------------- : Settings @@ -168,6 +176,7 @@ Settings::Settings() , symbol_grid (true) , symbol_grid_snap (false) , print_spacing (0.33) + , print_cutter_lines (CUTTER_ALL) , internal_scale (1.0) , internal_image_extension(true) #if USE_OLD_STYLE_UPDATE_CHECKER @@ -257,6 +266,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(Settings) { REFLECT(symbol_grid_snap); REFLECT(default_game); REFLECT(print_spacing); + REFLECT(print_cutter_lines); REFLECT(apprentice_location); REFLECT(internal_scale); REFLECT(internal_image_extension); diff --git a/src/data/settings.hpp b/src/data/settings.hpp index 0802c503..27c1db96 100644 --- a/src/data/settings.hpp +++ b/src/data/settings.hpp @@ -111,6 +111,15 @@ public: DECLARE_REFLECTION(); }; + +// ----------------------------------------------------------------------------- : Printing settings + +enum CutterLinesType +{ CUTTER_ALL +, CUTTER_NO_INTERSECTION +, CUTTER_NONE +}; + // ----------------------------------------------------------------------------- : Settings @@ -180,8 +189,9 @@ public: IndexMap& exportOptionsFor(const ExportTemplate& export_template); // --------------------------------------------------- : Printing - + double print_spacing; + CutterLinesType print_cutter_lines; // --------------------------------------------------- : Special game stuff String apprentice_location; diff --git a/src/gui/print_window.cpp b/src/gui/print_window.cpp index 0aec84cd..34691353 100644 --- a/src/gui/print_window.cpp +++ b/src/gui/print_window.cpp @@ -33,14 +33,18 @@ void PrintJob::init(const RealSize& page_size) { void PrintJob::measure_cards() { FOR_EACH(card, cards) { const StyleSheet& stylesheet = set->stylesheetFor(card); - RealSize size(stylesheet.card_width * 25.4 / stylesheet.card_dpi, stylesheet.card_height * 25.4 / stylesheet.card_dpi); + RealSize size_px(stylesheet.card_width, stylesheet.card_height); + RealSize size_mm(stylesheet.card_width * 25.4 / stylesheet.card_dpi, stylesheet.card_height * 25.4 / stylesheet.card_dpi); Radians rotation = 0.0; - bool rotated = abs(size.width - default_size.height) < abs(size.height - default_size.height); // try to align best to default card height + bool rotated = abs(size_mm.width - default_size_mm.height) < abs(size_mm.height - default_size_mm.height); // try to align best to default card height if (rotated) { - swap(size.width, size.height); + swap(size_mm.width, size_mm.height); + swap(size_px.width, size_px.height); rotation = rad90; } - CardLayout layout(card, size, rotation); + if (abs(size_mm.width - default_size_mm.width) < threshold_size.width) size_mm.width = default_size_mm.width; // snap to default_size_mm if we are close + if (abs(size_mm.height - default_size_mm.height) < threshold_size.height) size_mm.height = default_size_mm.height; + CardLayout layout(card, size_mm, size_px, rotation); card_layouts.push_back(layout); } std::sort(card_layouts.begin(), card_layouts.end()); @@ -52,18 +56,18 @@ void PrintJob::layout_cards() { while (true) { // try to find a card that will fit on the current row for (int i = 0; i < card_layouts.size(); ++i) { - if (already_laidout_cards.find(i) != already_laidout_cards.end()) continue; - if (card_layouts[i].size.width + row_width >= page_size.width) continue; - if (card_layouts[i].size.height + row_top >= page_size.height) continue; + if (already_laidout_cards.find(i) != already_laidout_cards.end()) continue; + if (card_layouts[i].size_mm.width + row_width >= page_size.width) continue; + if (card_layouts[i].size_mm.height + row_top >= page_size.height) continue; // the card fits - card_layouts[i].pos.width = row_width; + card_layouts[i].pos.width = row_width; card_layouts[i].pos.height = row_top; page_layouts[page_layouts.size()-1].push_back(card_layouts[i]); already_laidout_cards.insert(i); if (already_laidout_cards.size() == card_layouts.size()) return; // move to next spot on the row - row_width += card_layouts[i].size.width + spacing; - row_height = max(row_height, card_layouts[i].size.height + spacing); + row_width += card_layouts[i].size_mm.width + settings.print_spacing; + row_height = max(row_height, card_layouts[i].size_mm.height + settings.print_spacing); goto continue_outer; } // no card fits @@ -86,10 +90,6 @@ void PrintJob::layout_cards() { } } void PrintJob::align_cards() { - // align cards that are at most this far appart in millimeters - double threshold_top = 0.2 * default_size.width; - // consider cards that are this close to already be aligned - double threshold_bottom = 0.05; // for each page for (int p = 0; p < page_layouts.size(); ++p) { vector& page_layout = page_layouts[p]; @@ -120,7 +120,7 @@ void PrintJob::align_cards() { // check if all these cards can be moved to the right bool can_move = true; for (int h = 0; h < cards.size(); ++h) { - if (page_layout[cards[h]].pos.width + page_layout[cards[h]].size.width + difference > page_size.width) { + if (page_layout[cards[h]].pos.width + page_layout[cards[h]].size_mm.width + difference > page_size.width) { can_move = false; break; } @@ -144,13 +144,14 @@ void PrintJob::center_cards() { vector& page_layout = page_layouts[p]; RealSize page_margin(0.0, 0.0); for (int i = 0; i < page_layout.size(); ++i) { - double width = page_layout[i].pos.width + page_layout[i].size.width; - double height = page_layout[i].pos.height + page_layout[i].size.height; + double width = page_layout[i].pos.width + page_layout[i].size_mm.width; + double height = page_layout[i].pos.height + page_layout[i].size_mm.height; if (page_margin.width < width) page_margin.width = width; if (page_margin.height < height) page_margin.height = height; } page_margin.width = (page_size.width - page_margin.width) / 2; page_margin.height = (page_size.height - page_margin.height) / 2; + page_margins.push_back(page_margin); for (int i = 0; i < page_layout.size(); ++i) { page_layout[i].pos.width += page_margin.width; page_layout[i].pos.height += page_margin.height; @@ -176,14 +177,17 @@ public: private: PrintJobP job; ///< Cards to print DataViewer viewer; - double scale_x, scale_y; // priter pixel per mm + RealSize printer_px_per_mm; int pageCount() { return job->page_layouts.size(); } - - /// Draw a card according to it's CardLayout info - void drawCard(DC& dc, const PrintJob::CardLayout& card_layout); + + /// Draw cutter lines in the page margins, corresponding to the edges of cards + void drawCutterLines(DC& dc, PrintJobP& job, int page); + /// Draw cards according to their CardLayout info + void drawCards (DC& dc, PrintJobP& job, int page); + void drawCard (DC& dc, PrintJob::CardLayout& card_layout); }; CardsPrintout::CardsPrintout(PrintJobP const& job) @@ -213,39 +217,123 @@ void CardsPrintout::OnPreparePrinting() { bool CardsPrintout::OnPrintPage(int page) { DC& dc = *GetDC(); // page size in millimeters - int pw_mm, ph_mm; - GetPageSizeMM(&pw_mm, &ph_mm); + int page_width_mm, page_height_mm; + GetPageSizeMM(&page_width_mm, &page_height_mm); // page size in pixels - int pw_px, ph_px; - dc.GetSize(&pw_px, &ph_px); - // scale factors (pixels per mm) - scale_x = (double)pw_px / pw_mm; - scale_y = (double)ph_px / ph_mm; + int page_width_px, page_height_px; + dc.GetSize(&page_width_px, &page_height_px); + // scale factor (pixels per mm) + printer_px_per_mm = RealSize((double)page_width_px / page_width_mm, (double)page_height_px / page_height_mm); // print the cards that belong on this page + drawCards(dc, job, page); + if (settings.print_cutter_lines != CUTTER_NONE) drawCutterLines(dc, job, page); + return true; +} + +void CardsPrintout::drawCards(DC& dc, PrintJobP& job, int page) { FOR_EACH(card_layout, job->page_layouts[page - 1]) { drawCard(dc, card_layout); - } - return true; + } + dc.SetUserScale(printer_px_per_mm.width, printer_px_per_mm.height); } - -void CardsPrintout::drawCard(DC& dc, const PrintJob::CardLayout& card_layout) { - const StyleSheet& stylesheet = job->set->stylesheetFor(card_layout.card); +void CardsPrintout::drawCard(DC& dc, PrintJob::CardLayout& card_layout) { // draw card to its own buffer - int w = int(stylesheet.card_width), h = int(stylesheet.card_height); // in pixels - if (is_rad90(card_layout.rotation)) swap(w,h); - wxBitmap buffer(w,h,32); + wxBitmap buffer(card_layout.size_px.width, card_layout.size_px.height, 32); wxMemoryDC bufferDC; bufferDC.SelectObject(buffer); clearDC(bufferDC,*wxWHITE_BRUSH); - RotatedDC rdc(bufferDC, card_layout.rotation, stylesheet.getCardRect(), 1.0, QUALITY_AA, ROTATION_ATTACH_TOP_LEFT); + RotatedDC rdc(bufferDC, card_layout.rot, RealRect(0, 0, card_layout.size_px.width, card_layout.size_px.height), 1.0, QUALITY_AA, ROTATION_ATTACH_TOP_LEFT); viewer.setCard(card_layout.card); viewer.draw(rdc, *wxWHITE); bufferDC.SelectObject(wxNullBitmap); // draw card buffer to page dc - double px_per_mm = stylesheet.card_dpi / 25.4; - dc.SetUserScale(scale_x / px_per_mm, scale_y / px_per_mm); - dc.DrawBitmap(buffer, int(card_layout.pos.width * px_per_mm), int(card_layout.pos.height * px_per_mm)); + dc.SetUserScale(printer_px_per_mm.width / card_layout.px_per_mm.width, printer_px_per_mm.height / card_layout.px_per_mm.height); + dc.DrawBitmap(buffer, int(card_layout.pos.width * card_layout.px_per_mm.width), int(card_layout.pos.height * card_layout.px_per_mm.height)); } + +void CardsPrintout::drawCutterLines(DC& dc, PrintJobP& job, int page) { + const vector& page_layout = job->page_layouts[page - 1]; + const RealSize& page_margin = job->page_margins[page - 1]; + int page_width, page_height; + GetPageSizeMM(&page_width, &page_height); + + double vertical_line_size = min(10.0, page_margin.height - 3.0); + if (vertical_line_size > 0.0) { + for (int i = 0; i < page_layout.size(); ++i) { + double left_line = page_layout[i].pos.width; + double right_line = left_line + page_layout[i].size_mm.width; + bool draw_left_line = true; + bool draw_right_line = true; + if (settings.print_cutter_lines == CUTTER_NO_INTERSECTION) { + // check if another card is in the way of this cutter line + for (int j = 0; j < page_layout.size(); ++j) { + if (i == j) continue; + double other_left_line = page_layout[j].pos.width; + double other_right_line = other_left_line + page_layout[j].size_mm.width; + if (draw_left_line && left_line - other_left_line > job->threshold_bottom && other_right_line - left_line > job->threshold_bottom) { + draw_left_line = false; + if (!draw_right_line) break; + } + if (draw_right_line && right_line - other_left_line > job->threshold_bottom && other_right_line - right_line > job->threshold_bottom) { + draw_right_line = false; + if (!draw_left_line) break; + } + } + } + const RealSize& px_per_mm = page_layout[i].px_per_mm; + dc.SetUserScale(printer_px_per_mm.width / px_per_mm.width, printer_px_per_mm.height / px_per_mm.height); + if (draw_left_line) { + dc.DrawLine(wxPoint(px_per_mm.width * left_line, 0.0), wxPoint(px_per_mm.width * left_line, px_per_mm.height * vertical_line_size)); + dc.DrawLine(wxPoint(px_per_mm.width * left_line, px_per_mm.height * page_height), wxPoint(px_per_mm.width * left_line, px_per_mm.height * (page_height - vertical_line_size))); + } + if (draw_right_line) { + dc.DrawLine(wxPoint(px_per_mm.width * right_line, 0.0), wxPoint(px_per_mm.width * right_line, px_per_mm.height * vertical_line_size)); + dc.DrawLine(wxPoint(px_per_mm.width * right_line, px_per_mm.height * page_height), wxPoint(px_per_mm.width * right_line, px_per_mm.height * (page_height - vertical_line_size))); + } + } + } else { + queue_message(MESSAGE_WARNING, _ERROR_("v margin too small for cutter")); + } + + double horizontal_line_size = min(10.0, page_margin.width - 3.0); + if (horizontal_line_size > 0.0) { + for (int i = 0; i < page_layout.size(); ++i) { + double top_line = page_layout[i].pos.height; + double bottom_line = top_line + page_layout[i].size_mm.height; + bool draw_top_line = true; + bool draw_bottom_line = true; + if (settings.print_cutter_lines == CUTTER_NO_INTERSECTION) { + // check if another card is in the way of this cutter line + for (int j = 0; j < page_layout.size(); ++j) { + if (i == j) continue; + double other_top_line = page_layout[j].pos.height; + double other_bottom_line = other_top_line + page_layout[j].size_mm.height; + if (draw_top_line && top_line - other_top_line > job->threshold_bottom && other_bottom_line - top_line > job->threshold_bottom) { + draw_top_line = false; + if (!draw_bottom_line) break; + } + if (draw_bottom_line && bottom_line - other_top_line > job->threshold_bottom && other_bottom_line - bottom_line > job->threshold_bottom) { + draw_bottom_line = false; + if (!draw_top_line) break; + } + } + } + const RealSize& px_per_mm = page_layout[i].px_per_mm; + dc.SetUserScale(printer_px_per_mm.width / px_per_mm.width, printer_px_per_mm.height / px_per_mm.height); + if (draw_top_line) { + dc.DrawLine(wxPoint(0.0, px_per_mm.height * top_line), wxPoint(px_per_mm.width * horizontal_line_size, px_per_mm.height * top_line)); + dc.DrawLine(wxPoint(px_per_mm.width * page_width, px_per_mm.height * top_line), wxPoint(px_per_mm.width * (page_width - horizontal_line_size), px_per_mm.height * top_line)); + } + if (draw_bottom_line) { + dc.DrawLine(wxPoint(0.0, px_per_mm.height * bottom_line), wxPoint(px_per_mm.width * horizontal_line_size, px_per_mm.height * bottom_line)); + dc.DrawLine(wxPoint(px_per_mm.width * page_width, px_per_mm.height * bottom_line), wxPoint(px_per_mm.width * (page_width - horizontal_line_size), px_per_mm.height * bottom_line)); + } + } + } else { + queue_message(MESSAGE_WARNING, _ERROR_("h margin too small for cutter")); + } + dc.SetUserScale(printer_px_per_mm.width, printer_px_per_mm.height); +} // ----------------------------------------------------------------------------- : PrintWindow @@ -257,6 +345,12 @@ PrintJobP make_print_job(Window* parent, const SetP& set, const ExportCardSelect validator.SetRange(0, 100); wxTextCtrl* space = new wxTextCtrl(&wnd, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, 0L, validator); space->SetValue(wxString::Format(wxT("%lf"), settings.print_spacing)); + wxChoice* cutter = new wxChoice(&wnd, wxID_ANY); + cutter->Clear(); + cutter->Append(_LABEL_("cutter lines all")); + cutter->Append(_LABEL_("cutter lines no intersect")); + cutter->Append(_LABEL_("cutter lines none")); + cutter->SetSelection((int)settings.print_cutter_lines); // layout wxSizer* s = new wxBoxSizer(wxVERTICAL); wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL); @@ -265,6 +359,8 @@ PrintJobP make_print_job(Window* parent, const SetP& set, const ExportCardSelect wxSizer* s4 = new wxStaticBoxSizer(wxVERTICAL, &wnd, _TITLE_("settings")); s4->Add(new wxStaticText(&wnd, -1, _LABEL_("spacing print")), 0, wxALL, 8); s4->Add(space, 0, wxALL & ~wxTOP, 8); + s4->Add(new wxStaticText(&wnd, -1, _LABEL_("cutter lines print")), 0, wxALL, 8); + s4->Add(cutter, 0, wxALL & ~wxTOP, 8); s2->Add(s4, 1, wxEXPAND | (wxALL & ~wxLEFT), 8); s->Add(s2, 1, wxEXPAND); s->Add(wnd.CreateButtonSizer(wxOK | wxCANCEL) , 0, wxEXPAND | wxALL, 8); @@ -275,11 +371,13 @@ PrintJobP make_print_job(Window* parent, const SetP& set, const ExportCardSelect if (wnd.ShowModal() != wxID_OK) { return PrintJobP(); // cancel } else { - // make print job - double spacing; - space->GetValue().ToDouble(&spacing); - settings.print_spacing = spacing; - PrintJobP job = make_intrusive(set, wnd.getSelection(), spacing); + // save settings, make print job + String spacing = space->GetValue(); + if (spacing.empty()) spacing = _("0"); + spacing.ToDouble(&settings.print_spacing); + queue_message(MESSAGE_WARNING, wxString::Format(_("%f"),settings.print_spacing)); + settings.print_cutter_lines = (CutterLinesType)cutter->GetSelection(); + PrintJobP job = make_intrusive(set, wnd.getSelection()); return job; } } diff --git a/src/gui/print_window.hpp b/src/gui/print_window.hpp index 09438b6d..166528e0 100644 --- a/src/gui/print_window.hpp +++ b/src/gui/print_window.hpp @@ -22,28 +22,42 @@ DECLARE_POINTER_TYPE(PrintJob); class PrintJob : public IntrusivePtrBase { public: - PrintJob(const SetP& set, const vector& cards, double spacing) : set(set), cards(cards), spacing(spacing) { - default_size.width = set->stylesheet->card_width * 25.4 / set->stylesheet->card_dpi; - default_size.height = set->stylesheet->card_height * 25.4 / set->stylesheet->card_dpi; + PrintJob(const SetP& set, const vector& cards) + : set(set), cards(cards) { + default_size_mm.width = set->stylesheet->card_width * 25.4 / set->stylesheet->card_dpi; + default_size_mm.height = set->stylesheet->card_height * 25.4 / set->stylesheet->card_dpi; + + threshold_top = 0.75 * default_size_mm.width; + threshold_bottom = 0.1; + threshold_size = RealSize(0.05 * default_size_mm.width, 0.05 * default_size_mm.height); } SetP set; - vector cards; ///< Cards selected by the user for print - RealSize default_size; ///< Size of a card with the default stylesheet in millimetres - double spacing; ///< Spacing between cards in millimetres - + vector cards; ///< Cards selected by the user for print + RealSize default_size_mm; ///< Size of a card with the default stylesheet in millimetres + + // align cards that are at most this far appart in millimeters + double threshold_top; + // consider cards that are this close to already be aligned + double threshold_bottom; + // snap cards that are within this to the default size + RealSize threshold_size; + struct CardLayout { - CardLayout(const CardP& card, const RealSize& size, Radians rotation) - : card(card), size(size), rotation(rotation) {} + CardLayout(const CardP& card, const RealSize& size_mm, const RealSize& size_px, Radians rot) + : card(card), size_mm(size_mm), size_px(size_px), rot(rot) { + px_per_mm = RealSize(size_px.width / size_mm.width, size_px.height / size_mm.height); + } bool operator<(const CardLayout& that) const { - return size.width > that.size.width; // put the widest cards first + return size_mm.width > that.size_mm.width; // put the widest cards first } CardP card; - RealSize size; - Radians rotation; - + RealSize size_mm; + RealSize size_px; + RealSize px_per_mm; + Radians rot; RealSize pos; }; @@ -52,6 +66,7 @@ public: RealSize page_size; ///< Size of a page in millimetres vector card_layouts; ///< Locations of the cards on the pages vector> page_layouts; ///< The CardLayout grouped by page + vector page_margins; ///< The empty space on the sides of the pages /// Is this job uninitialized? inline bool empty() const { return page_layouts.empty(); }