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
+22 -4
View File
@@ -21,7 +21,7 @@
// ----------------------------------------------------------------------------- : Card // ----------------------------------------------------------------------------- : Card
Card::Card() Card::Card()
// for files made before we saved these, set the time to 'yesterday', generate a uid // for files made before we saved these, set the time to 'yesterday', generate a uid
: time_created (wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime()) : time_created (wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
, time_modified(wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime()) , time_modified(wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
, uid(generate_uid()) , uid(generate_uid())
@@ -80,7 +80,7 @@ void Card::link(const Set& set, const vector<CardP>& linked_cards, const String&
if ( if (
this_linked_uid.empty() || // Not a reference this_linked_uid.empty() || // Not a reference
all_existing_uids.find(this_linked_uid) == all_existing_uids.end() // Reference to nonexistent card all_existing_uids.find(this_linked_uid) == all_existing_uids.end() // Reference to nonexistent card
) free_link_count++; ) free_link_count++;
} }
if (free_link_count < linked_cards.size()) { if (free_link_count < linked_cards.size()) {
queue_message(MESSAGE_WARNING, _ERROR_("not enough free links")); queue_message(MESSAGE_WARNING, _ERROR_("not enough free links"));
@@ -265,7 +265,7 @@ void Card::updateLink(String old_uid, String new_uid) {
} }
} }
vector<pair<CardP, String>> Card::getLinkedCards(const Set& set) { vector<pair<CardP, String>> Card::getLinkedCards(const vector<CardP>& cards) {
unordered_map<String, String> links{ unordered_map<String, String> links{
{ linked_card_1, linked_relation_1 }, { linked_card_1, linked_relation_1 },
{ linked_card_2, linked_relation_2 }, { linked_card_2, linked_relation_2 },
@@ -273,13 +273,31 @@ vector<pair<CardP, String>> Card::getLinkedCards(const Set& set) {
{ linked_card_4, linked_relation_4 } { linked_card_4, linked_relation_4 }
}; };
vector<pair<CardP, String>> linked_cards; vector<pair<CardP, String>> linked_cards;
FOR_EACH(other_card, set.cards) { FOR_EACH(other_card, cards) {
if (links.find(other_card->uid) != links.end()) { if (links.find(other_card->uid) != links.end()) {
linked_cards.push_back(make_pair(other_card, links.at(other_card->uid))); linked_cards.push_back(make_pair(other_card, links.at(other_card->uid)));
} }
} }
return linked_cards; return linked_cards;
} }
vector<pair<CardP, String>> Card::getLinkedCards(const Set& set) {
return getLinkedCards(set.cards);
}
CardP Card::getOtherFace(const vector<CardP>& cards) {
unordered_set<String> faces;
if (linked_relation_1 == _("Front Face") || linked_relation_1 == _("Back Face")) faces.emplace(linked_card_1);
if (linked_relation_2 == _("Front Face") || linked_relation_2 == _("Back Face")) faces.emplace(linked_card_2);
if (linked_relation_3 == _("Front Face") || linked_relation_3 == _("Back Face")) faces.emplace(linked_card_3);
if (linked_relation_4 == _("Front Face") || linked_relation_4 == _("Back Face")) faces.emplace(linked_card_4);
FOR_EACH(other_card, cards) {
if (faces.find(other_card->uid) != faces.end()) return other_card;
}
return nullptr;
}
CardP Card::getOtherFace(const Set& set) {
return getOtherFace(set.cards);
}
IndexMap<FieldP, ValueP>& Card::extraDataFor(const StyleSheet& stylesheet) { IndexMap<FieldP, ValueP>& Card::extraDataFor(const StyleSheet& stylesheet) {
return extra_data.get(stylesheet.name(), stylesheet.extra_card_fields); return extra_data.get(stylesheet.name(), stylesheet.extra_card_fields);
+11 -8
View File
@@ -35,7 +35,7 @@ public:
Card(); Card();
/// Creates a card using the given game /// Creates a card using the given game
Card(const Game& game); Card(const Game& game);
/// The values on the fields of the card. /// The values on the fields of the card.
/** The indices should correspond to the card_fields in the Game */ /** The indices should correspond to the card_fields in the Game */
IndexMap<FieldP, ValueP> data; IndexMap<FieldP, ValueP> data;
@@ -60,26 +60,26 @@ public:
StyleSheetP stylesheet; StyleSheetP stylesheet;
/// Alternative options to use for this card, for this card's stylesheet /// Alternative options to use for this card, for this card's stylesheet
/** Optional; if not set use the styling data from the set. /** Optional; if not set use the styling data from the set.
* If stylesheet is set then contains data for the this->stylesheet, otherwise for set->stylesheet * If stylesheet is set then contains data for the this->stylesheet, otherwise for set->stylesheet
*/ */
IndexMap<FieldP,ValueP> styling_data; IndexMap<FieldP,ValueP> styling_data;
/// Is the styling_data set? /// Is the styling_data set?
bool has_styling; bool has_styling;
/// Extra values for specitic stylesheets, indexed by stylesheet name /// Extra values for specitic stylesheets, indexed by stylesheet name
DelayedIndexMaps<FieldP,ValueP> extra_data; DelayedIndexMaps<FieldP,ValueP> extra_data;
/// Styling information for a particular stylesheet /// Styling information for a particular stylesheet
IndexMap<FieldP, ValueP>& extraDataFor(const StyleSheet& stylesheet); IndexMap<FieldP, ValueP>& extraDataFor(const StyleSheet& stylesheet);
/// Keyword usage statistics /// Keyword usage statistics
vector<pair<const Value*,const Keyword*>> keyword_usage; vector<pair<const Value*,const Keyword*>> keyword_usage;
/// Get the identification of this card, an identification is something like a name, title, etc. /// Get the identification of this card, an identification is something like a name, title, etc.
/** May return "" */ /** May return "" */
String identification() const; String identification() const;
/// Does any field contains the given query string? /// Does any field contains the given query string?
bool contains(QuickFilterPart const& query) const; bool contains(QuickFilterPart const& query) const;
/// Link or unlink other cards to this card /// Link or unlink other cards to this card
void link(const Set& set, const vector<CardP>& linked_cards, const String& selected_relation, const String& linked_relation); void link(const Set& set, const vector<CardP>& linked_cards, const String& selected_relation, const String& linked_relation);
void link(const Set& set, CardP& linked_card, const String& selected_relation, const String& linked_relation); void link(const Set& set, CardP& linked_card, const String& selected_relation, const String& linked_relation);
@@ -89,7 +89,10 @@ public:
void copyLink(const Set& set, String old_uid, String new_uid); void copyLink(const Set& set, String old_uid, String new_uid);
void updateLink(String old_uid, String new_uid); void updateLink(String old_uid, String new_uid);
vector<pair<CardP, String>> getLinkedCards(const vector<CardP>& cards);
vector<pair<CardP, String>> getLinkedCards(const Set& set); vector<pair<CardP, String>> getLinkedCards(const Set& set);
CardP getOtherFace(const vector<CardP>& cards);
CardP getOtherFace(const Set& set);
/// Find a value in the data by name and type /// Find a value in the data by name and type
template <typename T> T& value(const String& name) { template <typename T> T& value(const String& name) {
@@ -112,7 +115,7 @@ public:
} }
throw InternalError(_("Expected a card field with name '")+name+_("'")); throw InternalError(_("Expected a card field with name '")+name+_("'"));
} }
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
+41 -40
View File
@@ -100,7 +100,7 @@ Bitmap export_bitmap(const SetP& set, const vector<CardP>& cards, bool scale_to_
if (scale_to_lowest_dpi) { if (scale_to_lowest_dpi) {
double dpi = max(set->stylesheetFor(card).card_dpi, 150.0); double dpi = max(set->stylesheetFor(card).card_dpi, 150.0);
scale *= lowest_dpi / dpi; scale *= lowest_dpi / dpi;
} }
scales_out.push_back(scale); scales_out.push_back(scale);
UnzoomedDataViewer viewer = UnzoomedDataViewer(scale, angle_radians); UnzoomedDataViewer viewer = UnzoomedDataViewer(scale, angle_radians);
viewer.setSet(set); viewer.setSet(set);
@@ -124,7 +124,7 @@ Bitmap export_bitmap(const SetP& set, const vector<CardP>& cards, bool scale_to_
globalDC.SelectObject(global_bitmap); globalDC.SelectObject(global_bitmap);
clearDC(globalDC, *wxWHITE_BRUSH); clearDC(globalDC, *wxWHITE_BRUSH);
int offset = 0; int offset = 0;
FOR_EACH(bitmap, bitmaps) { FOR_EACH(bitmap, bitmaps) {
offsets_out.push_back(offset); offsets_out.push_back(offset);
globalDC.SetDeviceOrigin(offset, 0); globalDC.SetDeviceOrigin(offset, 0);
globalDC.DrawBitmap(bitmap, 0, 0); globalDC.DrawBitmap(bitmap, 0, 0);
@@ -138,57 +138,58 @@ Bitmap export_bitmap(const SetP& set, const vector<CardP>& cards, bool scale_to_
Image export_image(const SetP& set, const CardP& card, const double zoom, const Radians angle_radians) { Image export_image(const SetP& set, const CardP& card, const double zoom, const Radians angle_radians) {
Bitmap bitmap = export_bitmap(set, card, zoom, angle_radians); Bitmap bitmap = export_bitmap(set, card, zoom, angle_radians);
Image img = bitmap.ConvertToImage(); Image img = bitmap.ConvertToImage();
String data = _("<mse-data-start>["); String data = _("<mse-data-start>[");
IndexMap<FieldP, ValueP>& card_data = card->data; IndexMap<FieldP, ValueP>& card_data = card->data;
boost::json::object& cardv = mse_to_json(card, set.get()); boost::json::object& cardv = mse_to_json(card, set.get());
boost::json::object& cardv_data = cardv["data"].as_object(); boost::json::object& cardv_data = cardv["data"].as_object();
if (!settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_notes_export()) cardv["notes"] = ""; StyleSheetP stylesheet = set->stylesheetForP(card);
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) { if (!settings.stylesheetSettingsFor(*stylesheet).card_notes_export()) cardv["notes"] = "";
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) {
ImageValue* value = dynamic_cast<ImageValue*>(it->get()); ImageValue* value = dynamic_cast<ImageValue*>(it->get());
if (value && !value->filename.empty()) { if (value && !value->filename.empty()) {
FieldP field = (*it)->fieldP; FieldP field = (*it)->fieldP;
StyleP style = set->stylesheetFor(card).card_style.at(field->index); StyleP style = stylesheet->card_style.at(field->index);
if (style) { if (style) {
style->update(set->getContext(card)); style->update(set->getContext(card));
std::string rect = style->getExternalRectString(zoom, 0).ToStdString(); std::string rect = style->getExternalRectString(zoom, 0).ToStdString();
cardv_data[field->name.ToStdString()] = rect; cardv_data[field->name.ToStdString()] = rect;
} }
} }
} }
data += json_ugly_print(cardv) + _("]<mse-data-end>"); data += json_ugly_print(cardv) + _("]<mse-data-end>");
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, data); img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, data);
return img; return img;
} }
Image export_image(const SetP& set, const vector<CardP>& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle_radians) { Image export_image(const SetP& set, const vector<CardP>& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle_radians) {
vector<double> scales; vector<double> scales;
vector<int> offsets; vector<int> offsets;
Bitmap bitmap = export_bitmap(set, cards, scale_to_lowest_dpi, padding, zoom, angle_radians, scales, offsets); Bitmap bitmap = export_bitmap(set, cards, scale_to_lowest_dpi, padding, zoom, angle_radians, scales, offsets);
Image img = bitmap.ConvertToImage(); Image img = bitmap.ConvertToImage();
String data = _("<mse-data-start>["); String data = _("<mse-data-start>[");
for (int i = 0; i < cards.size(); ++i) { for (int i = 0; i < cards.size(); ++i) {
if (i > 0) data += _(","); if (i > 0) data += _(",");
CardP card = cards[i]; CardP card = cards[i];
IndexMap<FieldP, ValueP>& card_data = card->data; IndexMap<FieldP, ValueP>& card_data = card->data;
boost::json::object& cardv = mse_to_json(card, set.get()); boost::json::object& cardv = mse_to_json(card, set.get());
boost::json::object& cardv_data = cardv["data"].as_object(); boost::json::object& cardv_data = cardv["data"].as_object();
if (!settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_notes_export()) cardv["notes"] = ""; StyleSheetP stylesheet = set->stylesheetForP(card);
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) { if (!settings.stylesheetSettingsFor(*stylesheet).card_notes_export()) cardv["notes"] = "";
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) {
ImageValue* value = dynamic_cast<ImageValue*>(it->get()); ImageValue* value = dynamic_cast<ImageValue*>(it->get());
if (value && !value->filename.empty()) { if (value && !value->filename.empty()) {
FieldP field = (*it)->fieldP; FieldP field = (*it)->fieldP;
StyleSheetP stylesheet = set->stylesheetForP(card);
StyleP style = stylesheet->card_style.at(field->index); StyleP style = stylesheet->card_style.at(field->index);
if (style) { if (style) {
style->update(set->getContext(card)); style->update(set->getContext(card));
std::string rect = style->getExternalRectString(scales[i], offsets[i]).ToStdString(); std::string rect = style->getExternalRectString(scales[i], offsets[i]).ToStdString();
cardv_data[field->name.ToStdString()] = rect; cardv_data[field->name.ToStdString()] = rect;
} }
} }
} }
data += json_ugly_print(cardv); data += json_ugly_print(cardv);
} }
data += _("]<mse-data-end>"); data += _("]<mse-data-end>");
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, data); img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, data);
return img; return img;
@@ -197,11 +198,11 @@ Image export_image(const SetP& set, const vector<CardP>& cards, bool scale_to_lo
void export_image(const SetP& set, const CardP& card, const String& filename) { void export_image(const SetP& set, const CardP& card, const String& filename) {
Image img = export_image(set, card); Image img = export_image(set, card);
img.SaveFile(filename); // can't use Bitmap::saveFile, it wants to know the file type img.SaveFile(filename); // can't use Bitmap::saveFile, it wants to know the file type
// but image.saveFile determines it automagicly // but image.saveFile determines it automagicly
} }
void export_image(const SetP& set, const vector<CardP>& cards, void export_image(const SetP& set, const vector<CardP>& cards,
const String& path, const String& filename_template, FilenameConflicts conflicts) const String& path, const String& filename_template, FilenameConflicts conflicts)
{ {
wxBusyCursor busy; wxBusyCursor busy;
// Script // Script
+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, _("")); linked_relation = new wxTextCtrl(this, wxID_ANY, _(""));
relation_type = new wxChoice(this, ID_CARD_LINK_TYPE, wxDefaultPosition, wxDefaultSize, 0, nullptr); relation_type = new wxChoice(this, ID_CARD_LINK_TYPE, wxDefaultPosition, wxDefaultSize, 0, nullptr);
relation_type->Clear(); relation_type->Clear();
relation_type->Append("Front Face // Back Face");
FOR_EACH(link, set->game->card_links) { FOR_EACH(link, set->game->card_links) {
relation_type->Append(link); relation_type->Append(link);
} }
@@ -38,18 +39,18 @@ CardLinkWindow::CardLinkWindow(Window* parent, const SetP& set, const CardP& sel
// init sizers // init sizers
if (sizer) { if (sizer) {
wxSizer* s = new wxBoxSizer(wxVERTICAL); wxSizer* s = new wxBoxSizer(wxVERTICAL);
s->Add(new wxStaticText(this, -1, _LABEL_("linked cards relation")), 0, wxALL, 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(relation_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("selected card")), 0, wxALL, 4); s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("selected card")), 0, wxALL, 4);
s->Add(selected_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8); s->Add(selected_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("linked cards")), 0, wxALL, 4); s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("linked cards")), 0, wxALL, 4);
s->Add(linked_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8); 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(new wxStaticText(this, wxID_ANY, _LABEL_("select linked cards")), 0, wxALL & ~wxBOTTOM, 8);
s->Add(list, 1, wxEXPAND | wxALL, 8); s->Add(list, 1, wxEXPAND | wxALL, 8);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL); wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(sel_none, 0, wxEXPAND | wxRIGHT, 8); s2->Add(sel_none, 0, wxEXPAND | wxRIGHT, 8);
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8); s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
s->Add(s2, 0, wxEXPAND | (wxALL & ~wxTOP), 8); s->Add(s2, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->SetSizeHints(this); s->SetSizeHints(this);
SetSizer(s); SetSizer(s);
SetSize(600,500); SetSize(600,500);
@@ -105,7 +106,7 @@ void CardLinkWindow::onOk(wxCommandEvent&) {
} }
BEGIN_EVENT_TABLE(CardLinkWindow, wxDialog) BEGIN_EVENT_TABLE(CardLinkWindow, wxDialog)
EVT_BUTTON (ID_SELECT_NONE, CardLinkWindow::onSelectNone) EVT_BUTTON (ID_SELECT_NONE, CardLinkWindow::onSelectNone)
EVT_BUTTON (wxID_OK, CardLinkWindow::onOk) EVT_BUTTON (wxID_OK, CardLinkWindow::onOk)
EVT_CHOICE (ID_CARD_LINK_TYPE, CardLinkWindow::onRelationTypeChange) EVT_CHOICE (ID_CARD_LINK_TYPE, CardLinkWindow::onRelationTypeChange)
END_EVENT_TABLE () END_EVENT_TABLE ()
+109 -68
View File
@@ -29,45 +29,81 @@ void PrintJob::init(const RealSize& page_size) {
layout_cards(); layout_cards();
align_cards(); align_cards();
center_cards(); center_cards();
} }
void PrintJob::measure_cards() {
void PrintJob::measure_cards() {
unordered_set<CardP> already_measured_cards;
FOR_EACH(card, cards) { FOR_EACH(card, cards) {
const StyleSheet& stylesheet = set->stylesheetFor(card); if (already_measured_cards.find(card) != already_measured_cards.end()) continue;
RealSize size_px(stylesheet.card_width, stylesheet.card_height); already_measured_cards.emplace(card);
RealSize size_mm(stylesheet.card_width * 25.4 / stylesheet.card_dpi, stylesheet.card_height * 25.4 / stylesheet.card_dpi); card_layouts.push_back(measure_card(card));
Radians rotation = 0.0; CardP other_face = card->getOtherFace(cards);
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 (other_face && already_measured_cards.find(other_face) == already_measured_cards.end()) {
if (rotated) { already_measured_cards.emplace(other_face);
swap(size_mm.width, size_mm.height); card_layouts.push_back(measure_card(other_face));
swap(size_px.width, size_px.height); int index = card_layouts.size()-1;
rotation = rad90; 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; sorted_layouts = vector<CardLayout>(card_layouts);
CardLayout layout(card, size_mm, size_px, rotation); std::sort(sorted_layouts.begin(), sorted_layouts.end());
card_layouts.push_back(layout); }
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() { void PrintJob::layout_cards() {
page_layouts.push_back(vector<CardLayout>()); page_layouts.push_back(vector<CardLayout>());
double row_top = 0.0, row_height = 0.0, row_width = 0.0; 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) { while (true) {
// try to find a card that will fit on the current row // try to find a card that will fit on the current row
for (int i = 0; i < card_layouts.size(); ++i) { FOR_EACH(layout, sorted_layouts) {
if (already_laidout_cards.find(i) != already_laidout_cards.end()) continue; if (already_laidout_cards.find(layout.card) != already_laidout_cards.end()) continue;
if (card_layouts[i].size_mm.width + row_width >= page_size.width) continue; if (layout.other_face >= 0) {
if (card_layouts[i].size_mm.height + row_top >= page_size.height) continue; CardLayout& other_layout = card_layouts[layout.other_face];
// the card fits if ( layout.size_mm.width + other_layout.size_mm.width + settings.print_spacing + row_width >= page_size.width) continue;
card_layouts[i].pos.width = row_width; if (max(layout.size_mm.height, other_layout.size_mm.height) + row_top >= page_size.height) continue;
card_layouts[i].pos.height = row_top; // the card and its other face fit
page_layouts[page_layouts.size()-1].push_back(card_layouts[i]); layout.pos.width = row_width;
already_laidout_cards.insert(i); layout.pos.height = row_top;
if (already_laidout_cards.size() == card_layouts.size()) return; page_layouts[page_layouts.size()-1].push_back(layout);
// move to next spot on the row already_laidout_cards.emplace(layout.card);
row_width += card_layouts[i].size_mm.width + settings.print_spacing; other_layout.pos.width = row_width + layout.size_mm.width + settings.print_spacing;
row_height = max(row_height, card_layouts[i].size_mm.height + 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; goto continue_outer;
} }
// no card fits // no card fits
@@ -88,7 +124,8 @@ void PrintJob::layout_cards() {
row_width = row_height = 0.0; row_width = row_height = 0.0;
continue_outer:; continue_outer:;
} }
} }
void PrintJob::align_cards() { void PrintJob::align_cards() {
// for each page // for each page
for (int p = 0; p < page_layouts.size(); ++p) { for (int p = 0; p < page_layouts.size(); ++p) {
@@ -138,7 +175,8 @@ void PrintJob::align_cards() {
continue_outer:; continue_outer:;
} }
} }
} }
void PrintJob::center_cards() { void PrintJob::center_cards() {
for (int p = 0; p < page_layouts.size(); ++p) { for (int p = 0; p < page_layouts.size(); ++p) {
vector<CardLayout>& page_layout = page_layouts[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) { void CardsPrintout::drawCards(DC& dc, PrintJobP& job, int page) {
FOR_EACH(card_layout, job->page_layouts[page - 1]) { FOR_EACH(card_layout, job->page_layouts[page - 1]) {
drawCard(dc, card_layout); 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) { void CardsPrintout::drawCard(DC& dc, PrintJob::CardLayout& card_layout) {
// draw card to its own buffer // upscale to printer dpi
wxBitmap buffer(card_layout.size_px.width, card_layout.size_px.height, 32); 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; wxMemoryDC bufferDC;
bufferDC.SelectObject(buffer); bufferDC.SelectObject(buffer);
clearDC(bufferDC,*wxWHITE_BRUSH); 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.setCard(card_layout.card);
viewer.draw(rdc, *wxWHITE); viewer.draw(rdc, *wxWHITE);
bufferDC.SelectObject(wxNullBitmap); bufferDC.SelectObject(wxNullBitmap);
// draw card buffer to page dc // render card dc 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.SetDeviceOrigin(int(printer_px_per_mm.width * card_layout.pos.width), int(printer_px_per_mm.height * card_layout.pos.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)); dc.DrawBitmap(buffer, 0, 0);
} }
void CardsPrintout::drawCutterLines(DC& dc, PrintJobP& job, int page) { 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]; const RealSize& page_margin = job->page_margins[page - 1];
int page_width, page_height; int page_width, page_height;
GetPageSizeMM(&page_width, &page_height); GetPageSizeMM(&page_width, &page_height);
double vertical_line_size = min(10.0, page_margin.height - 3.0); double vertical_line_size = min(10.0, page_margin.height - 3.0);
if (vertical_line_size > 0.0) { if (vertical_line_size > 0.0) {
for (int i = 0; i < page_layout.size(); ++i) { for (int i = 0; i < page_layouts.size(); ++i) {
double left_line = page_layout[i].pos.width; double left_line = page_layouts[i].pos.width;
double right_line = left_line + page_layout[i].size_mm.width; double right_line = left_line + page_layouts[i].size_mm.width;
bool draw_left_line = true; bool draw_left_line = true;
bool draw_right_line = true; bool draw_right_line = true;
if (settings.print_cutter_lines == CUTTER_NO_INTERSECTION) { if (settings.print_cutter_lines == CUTTER_NO_INTERSECTION) {
// check if another card is in the way of this cutter line // 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; if (i == j) continue;
double other_left_line = page_layout[j].pos.width; double other_left_line = page_layouts[j].pos.width;
double other_right_line = other_left_line + page_layout[j].size_mm.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) { if (draw_left_line && left_line - other_left_line > job->threshold_bottom && other_right_line - left_line > job->threshold_bottom) {
draw_left_line = false; draw_left_line = false;
if (!draw_right_line) break; 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; if (draw_left_line) {
dc.SetUserScale(printer_px_per_mm.width / px_per_mm.width, printer_px_per_mm.height / px_per_mm.height); int left = printer_px_per_mm.width * left_line;
if (draw_left_line) { dc.DrawLine(wxPoint(left, 0.0), wxPoint(left, printer_px_per_mm.height * vertical_line_size));
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(left, printer_px_per_mm.height * page_height), wxPoint(left, printer_px_per_mm.height * (page_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) { 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)); int right = printer_px_per_mm.width * right_line;
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))); 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 { } 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); double horizontal_line_size = min(10.0, page_margin.width - 3.0);
if (horizontal_line_size > 0.0) { if (horizontal_line_size > 0.0) {
for (int i = 0; i < page_layout.size(); ++i) { for (int i = 0; i < page_layouts.size(); ++i) {
double top_line = page_layout[i].pos.height; double top_line = page_layouts[i].pos.height;
double bottom_line = top_line + page_layout[i].size_mm.height; double bottom_line = top_line + page_layouts[i].size_mm.height;
bool draw_top_line = true; bool draw_top_line = true;
bool draw_bottom_line = true; bool draw_bottom_line = true;
if (settings.print_cutter_lines == CUTTER_NO_INTERSECTION) { if (settings.print_cutter_lines == CUTTER_NO_INTERSECTION) {
// check if another card is in the way of this cutter line // 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; if (i == j) continue;
double other_top_line = page_layout[j].pos.height; double other_top_line = page_layouts[j].pos.height;
double other_bottom_line = other_top_line + page_layout[j].size_mm.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) { if (draw_top_line && top_line - other_top_line > job->threshold_bottom && other_bottom_line - top_line > job->threshold_bottom) {
draw_top_line = false; draw_top_line = false;
if (!draw_bottom_line) break; 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) { 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)); int top = printer_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)); 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) { 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)); int bottom = printer_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)); 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 { } else {
queue_message(MESSAGE_WARNING, _ERROR_("h margin too small for cutter")); queue_message(MESSAGE_WARNING, _ERROR_("h margin too small for cutter"));
} }
dc.SetUserScale(printer_px_per_mm.width, printer_px_per_mm.height);
} }
// ----------------------------------------------------------------------------- : PrintWindow // ----------------------------------------------------------------------------- : PrintWindow
+7 -7
View File
@@ -44,27 +44,26 @@ public:
RealSize threshold_size; RealSize threshold_size;
struct CardLayout { struct CardLayout {
CardLayout(const CardP& card, const RealSize& size_mm, const RealSize& size_px, Radians rot) 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(rot) { : card(card), size_mm(size_mm), size_px(size_px), rot(rotation), other_face(-1) {}
px_per_mm = RealSize(size_px.width / size_mm.width, size_px.height / size_mm.height);
}
bool operator<(const CardLayout& that) const { bool operator<(const CardLayout& that) const {
return size_mm.width > that.size_mm.width; // put the widest cards first return size_mm.width > that.size_mm.width; // put the widest cards first
} }
CardP card; CardP card;
RealSize size_mm; RealSize size_mm;
RealSize size_px; RealSize size_px;
RealSize px_per_mm; Radians rot;
Radians rot;
RealSize pos; RealSize pos;
int other_face;
}; };
void init(const RealSize& page_size); void init(const RealSize& page_size);
RealSize page_size; ///< Size of a page in millimetres RealSize page_size; ///< Size of a page in millimetres
vector<CardLayout> card_layouts; ///< Locations of the cards on the pages 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<vector<CardLayout>> page_layouts; ///< The CardLayout grouped by page
vector<RealSize> page_margins; ///< The empty space on the sides of the pages vector<RealSize> page_margins; ///< The empty space on the sides of the pages
@@ -74,6 +73,7 @@ public:
private: private:
// calculate the width and height of each card in millimeters // calculate the width and height of each card in millimeters
void measure_cards(); void measure_cards();
CardLayout measure_card(const CardP& card);
// calculate where the cards go on the pages // calculate where the cards go on the pages
void layout_cards(); void layout_cards();
// if two cards are almost aligned, align them // if two cards are almost aligned, align them