rewrite print logic once again

- move Front Face // Back Face card link to exe level
- print front faces and back faces together
- use printer dpi instead of stylesheet dpi
This commit is contained in:
GenevensiS
2025-11-30 10:37:59 +01:00
parent 22b3a3e8b2
commit 2d6c0d2aee
6 changed files with 206 additions and 142 deletions
+16 -15
View File
@@ -25,6 +25,7 @@ CardLinkWindow::CardLinkWindow(Window* parent, const SetP& set, const CardP& sel
linked_relation = new wxTextCtrl(this, wxID_ANY, _(""));
relation_type = new wxChoice(this, ID_CARD_LINK_TYPE, wxDefaultPosition, wxDefaultSize, 0, nullptr);
relation_type->Clear();
relation_type->Append("Front Face // Back Face");
FOR_EACH(link, set->game->card_links) {
relation_type->Append(link);
}
@@ -38,18 +39,18 @@ CardLinkWindow::CardLinkWindow(Window* parent, const SetP& set, const CardP& sel
// init sizers
if (sizer) {
wxSizer* s = new wxBoxSizer(wxVERTICAL);
s->Add(new wxStaticText(this, -1, _LABEL_("linked cards relation")), 0, wxALL, 8);
s->Add(relation_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("selected card")), 0, wxALL, 4);
s->Add(selected_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("linked cards")), 0, wxALL, 4);
s->Add(linked_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("select linked cards")), 0, wxALL & ~wxBOTTOM, 8);
s->Add(list, 1, wxEXPAND | wxALL, 8);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(sel_none, 0, wxEXPAND | wxRIGHT, 8);
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
s->Add(s2, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _LABEL_("linked cards relation")), 0, wxALL, 8);
s->Add(relation_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("selected card")), 0, wxALL, 4);
s->Add(selected_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("linked cards")), 0, wxALL, 4);
s->Add(linked_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("select linked cards")), 0, wxALL & ~wxBOTTOM, 8);
s->Add(list, 1, wxEXPAND | wxALL, 8);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(sel_none, 0, wxEXPAND | wxRIGHT, 8);
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
s->Add(s2, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->SetSizeHints(this);
SetSizer(s);
SetSize(600,500);
@@ -105,7 +106,7 @@ void CardLinkWindow::onOk(wxCommandEvent&) {
}
BEGIN_EVENT_TABLE(CardLinkWindow, wxDialog)
EVT_BUTTON (ID_SELECT_NONE, CardLinkWindow::onSelectNone)
EVT_BUTTON (wxID_OK, CardLinkWindow::onOk)
EVT_CHOICE (ID_CARD_LINK_TYPE, CardLinkWindow::onRelationTypeChange)
EVT_BUTTON (ID_SELECT_NONE, CardLinkWindow::onSelectNone)
EVT_BUTTON (wxID_OK, CardLinkWindow::onOk)
EVT_CHOICE (ID_CARD_LINK_TYPE, CardLinkWindow::onRelationTypeChange)
END_EVENT_TABLE ()
+109 -68
View File
@@ -29,45 +29,81 @@ void PrintJob::init(const RealSize& page_size) {
layout_cards();
align_cards();
center_cards();
}
void PrintJob::measure_cards() {
}
void PrintJob::measure_cards() {
unordered_set<CardP> already_measured_cards;
FOR_EACH(card, cards) {
const StyleSheet& stylesheet = set->stylesheetFor(card);
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_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_mm.width, size_mm.height);
swap(size_px.width, size_px.height);
rotation = rad90;
if (already_measured_cards.find(card) != already_measured_cards.end()) continue;
already_measured_cards.emplace(card);
card_layouts.push_back(measure_card(card));
CardP other_face = card->getOtherFace(cards);
if (other_face && already_measured_cards.find(other_face) == already_measured_cards.end()) {
already_measured_cards.emplace(other_face);
card_layouts.push_back(measure_card(other_face));
int index = card_layouts.size()-1;
card_layouts[index].other_face = index-1;
card_layouts[index-1].other_face = index;
}
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);
}
sorted_layouts = vector<CardLayout>(card_layouts);
std::sort(sorted_layouts.begin(), sorted_layouts.end());
}
PrintJob::CardLayout PrintJob::measure_card(const CardP& card) {
const StyleSheet& stylesheet = set->stylesheetFor(card);
RealSize size_mm(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);
Radians rotation = 0.0;
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_mm.width, size_mm.height);
swap(size_px.width, size_px.height);
rotation = rad90;
}
std::sort(card_layouts.begin(), card_layouts.end());
}
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;
return CardLayout(card, size_mm, size_px, rotation);
}
void PrintJob::layout_cards() {
page_layouts.push_back(vector<CardLayout>());
double row_top = 0.0, row_height = 0.0, row_width = 0.0;
unordered_set<int> already_laidout_cards;
unordered_set<CardP> already_laidout_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_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.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_mm.width + settings.print_spacing;
row_height = max(row_height, card_layouts[i].size_mm.height + settings.print_spacing);
FOR_EACH(layout, sorted_layouts) {
if (already_laidout_cards.find(layout.card) != already_laidout_cards.end()) continue;
if (layout.other_face >= 0) {
CardLayout& other_layout = card_layouts[layout.other_face];
if ( layout.size_mm.width + other_layout.size_mm.width + settings.print_spacing + row_width >= page_size.width) continue;
if (max(layout.size_mm.height, other_layout.size_mm.height) + row_top >= page_size.height) continue;
// the card and its other face fit
layout.pos.width = row_width;
layout.pos.height = row_top;
page_layouts[page_layouts.size()-1].push_back(layout);
already_laidout_cards.emplace(layout.card);
other_layout.pos.width = row_width + layout.size_mm.width + settings.print_spacing;
other_layout.pos.height = row_top;
page_layouts[page_layouts.size()-1].push_back(other_layout);
already_laidout_cards.emplace(other_layout.card);
if (already_laidout_cards.size() >= card_layouts.size()) return;
// move to next spot on the row
row_width += layout.size_mm.width + other_layout.size_mm.width + 2 * settings.print_spacing;
row_height = max(max(row_height, layout.size_mm.height + settings.print_spacing), other_layout.size_mm.height + settings.print_spacing);
}
else {
if (layout.size_mm.width + row_width >= page_size.width) continue;
if (layout.size_mm.height + row_top >= page_size.height) continue;
// the card fits
layout.pos.width = row_width;
layout.pos.height = row_top;
page_layouts[page_layouts.size()-1].push_back(layout);
already_laidout_cards.emplace(layout.card);
if (already_laidout_cards.size() >= card_layouts.size()) return;
// move to next spot on the row
row_width += layout.size_mm.width + settings.print_spacing;
row_height = max(row_height, layout.size_mm.height + settings.print_spacing);
}
goto continue_outer;
}
// no card fits
@@ -88,7 +124,8 @@ void PrintJob::layout_cards() {
row_width = row_height = 0.0;
continue_outer:;
}
}
}
void PrintJob::align_cards() {
// for each page
for (int p = 0; p < page_layouts.size(); ++p) {
@@ -138,7 +175,8 @@ void PrintJob::align_cards() {
continue_outer:;
}
}
}
}
void PrintJob::center_cards() {
for (int p = 0; p < page_layouts.size(); ++p) {
vector<CardLayout>& page_layout = page_layouts[p];
@@ -233,43 +271,47 @@ bool CardsPrintout::OnPrintPage(int page) {
void CardsPrintout::drawCards(DC& dc, PrintJobP& job, int page) {
FOR_EACH(card_layout, job->page_layouts[page - 1]) {
drawCard(dc, card_layout);
}
dc.SetUserScale(printer_px_per_mm.width, printer_px_per_mm.height);
}
dc.SetDeviceOrigin(0, 0);
//dc.SetUserScale(printer_px_per_mm.width, printer_px_per_mm.height);
}
void CardsPrintout::drawCard(DC& dc, PrintJob::CardLayout& card_layout) {
// draw card to its own buffer
wxBitmap buffer(card_layout.size_px.width, card_layout.size_px.height, 32);
// upscale to printer dpi
double card_px_per_mm_width = card_layout.size_px.width / card_layout.size_mm.width;
double scale = printer_px_per_mm.width / card_px_per_mm_width;
// render card to its own dc
wxBitmap buffer(card_layout.size_px.width * scale, card_layout.size_px.height * scale, 32);
wxMemoryDC bufferDC;
bufferDC.SelectObject(buffer);
clearDC(bufferDC,*wxWHITE_BRUSH);
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);
RotatedDC rdc(bufferDC, card_layout.rot, RealRect(0, 0, card_layout.size_px.width, card_layout.size_px.height), scale, QUALITY_AA, ROTATION_ATTACH_TOP_LEFT);
viewer.setCard(card_layout.card);
viewer.draw(rdc, *wxWHITE);
bufferDC.SelectObject(wxNullBitmap);
// draw card buffer to page dc
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));
// render card dc to page dc
dc.SetDeviceOrigin(int(printer_px_per_mm.width * card_layout.pos.width), int(printer_px_per_mm.height * card_layout.pos.height));
dc.DrawBitmap(buffer, 0, 0);
}
void CardsPrintout::drawCutterLines(DC& dc, PrintJobP& job, int page) {
const vector<PrintJob::CardLayout>& page_layout = job->page_layouts[page - 1];
const vector<PrintJob::CardLayout>& page_layouts = 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;
for (int i = 0; i < page_layouts.size(); ++i) {
double left_line = page_layouts[i].pos.width;
double right_line = left_line + page_layouts[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) {
for (int j = 0; j < page_layouts.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;
double other_left_line = page_layouts[j].pos.width;
double other_right_line = other_left_line + page_layouts[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;
@@ -280,15 +322,15 @@ void CardsPrintout::drawCutterLines(DC& dc, PrintJobP& job, int page) {
}
}
}
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_left_line) {
int left = printer_px_per_mm.width * left_line;
dc.DrawLine(wxPoint(left, 0.0), wxPoint(left, printer_px_per_mm.height * vertical_line_size));
dc.DrawLine(wxPoint(left, printer_px_per_mm.height * page_height), wxPoint(left, printer_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)));
int right = printer_px_per_mm.width * right_line;
dc.DrawLine(wxPoint(right, 0.0), wxPoint(right, printer_px_per_mm.height * vertical_line_size));
dc.DrawLine(wxPoint(right, printer_px_per_mm.height * page_height), wxPoint(right, printer_px_per_mm.height * (page_height - vertical_line_size)));
}
}
} else {
@@ -297,17 +339,17 @@ void CardsPrintout::drawCutterLines(DC& dc, PrintJobP& job, int page) {
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;
for (int i = 0; i < page_layouts.size(); ++i) {
double top_line = page_layouts[i].pos.height;
double bottom_line = top_line + page_layouts[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) {
for (int j = 0; j < page_layouts.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;
double other_top_line = page_layouts[j].pos.height;
double other_bottom_line = other_top_line + page_layouts[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;
@@ -318,21 +360,20 @@ void CardsPrintout::drawCutterLines(DC& dc, PrintJobP& job, int page) {
}
}
}
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));
int top = printer_px_per_mm.height * top_line;
dc.DrawLine(wxPoint(0.0, top), wxPoint(printer_px_per_mm.width * horizontal_line_size, top));
dc.DrawLine(wxPoint(printer_px_per_mm.width * page_width, top), wxPoint(printer_px_per_mm.width * (page_width - horizontal_line_size), top));
}
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));
int bottom = printer_px_per_mm.height * bottom_line;
dc.DrawLine(wxPoint(0.0, bottom), wxPoint(printer_px_per_mm.width * horizontal_line_size, bottom));
dc.DrawLine(wxPoint(printer_px_per_mm.width * page_width, bottom), wxPoint(printer_px_per_mm.width * (page_width - horizontal_line_size), bottom));
}
}
} 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
+7 -7
View File
@@ -44,27 +44,26 @@ public:
RealSize threshold_size;
struct CardLayout {
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);
}
CardLayout(const CardP& card, const RealSize& size_mm, const RealSize& size_px, const Radians& rotation)
: card(card), size_mm(size_mm), size_px(size_px), rot(rotation), other_face(-1) {}
bool operator<(const CardLayout& that) const {
return size_mm.width > that.size_mm.width; // put the widest cards first
}
CardP card;
RealSize size_mm;
RealSize size_mm;
RealSize size_px;
RealSize px_per_mm;
Radians rot;
Radians rot;
RealSize pos;
int other_face;
};
void init(const RealSize& page_size);
RealSize page_size; ///< Size of a page in millimetres
vector<CardLayout> card_layouts; ///< Locations of the cards on the pages
vector<CardLayout> sorted_layouts; ///< Same as card_layouts, but sorted from widest to narrowest
vector<vector<CardLayout>> page_layouts; ///< The CardLayout grouped by page
vector<RealSize> page_margins; ///< The empty space on the sides of the pages
@@ -74,6 +73,7 @@ public:
private:
// calculate the width and height of each card in millimeters
void measure_cards();
CardLayout measure_card(const CardP& card);
// calculate where the cards go on the pages
void layout_cards();
// if two cards are almost aligned, align them