Add cutter lines option when printing

This commit is contained in:
GenevensiS
2025-08-01 13:54:12 +02:00
committed by GitHub
parent d74879b6dd
commit 2b9bc1f296
4 changed files with 193 additions and 60 deletions
+11 -1
View File
@@ -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);
+11 -1
View File
@@ -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<FieldP,ValueP>& exportOptionsFor(const ExportTemplate& export_template);
// --------------------------------------------------- : Printing
double print_spacing;
CutterLinesType print_cutter_lines;
// --------------------------------------------------- : Special game stuff
String apprentice_location;
+143 -45
View File
@@ -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<CardLayout>& 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<CardLayout>& 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<PrintJob::CardLayout>& 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<PrintJob>(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<PrintJob>(set, wnd.getSelection());
return job;
}
}
+28 -13
View File
@@ -22,28 +22,42 @@ DECLARE_POINTER_TYPE(PrintJob);
class PrintJob : public IntrusivePtrBase<PrintJob> {
public:
PrintJob(const SetP& set, const vector<CardP>& 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<CardP>& 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<CardP> 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<CardP> 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<CardLayout> card_layouts; ///< Locations of the cards on the pages
vector<vector<CardLayout>> page_layouts; ///< The CardLayout grouped by page
vector<RealSize> page_margins; ///< The empty space on the sides of the pages
/// Is this job uninitialized?
inline bool empty() const { return page_layouts.empty(); }