mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
rewrite print layout logic
allow for different size cards
This commit is contained in:
+5
-12
@@ -153,13 +153,6 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(StyleSheetSettings) {
|
||||
REFLECT(card_spellcheck_enabled);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Printing
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(PageLayoutType) {
|
||||
VALUE_N("no space", LAYOUT_NO_SPACE);
|
||||
VALUE_N("equal space", LAYOUT_EQUAL_SPACE);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Settings
|
||||
|
||||
Settings settings;
|
||||
@@ -174,8 +167,8 @@ Settings::Settings()
|
||||
, symbol_grid_size (30)
|
||||
, symbol_grid (true)
|
||||
, symbol_grid_snap (false)
|
||||
, print_layout (LAYOUT_NO_SPACE)
|
||||
, internal_scale (1.0)
|
||||
, print_spacing (0.33)
|
||||
, internal_scale (1.0)
|
||||
, internal_image_extension(true)
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
, updates_url (_("https://magicseteditor.boards.net/page/downloads"))
|
||||
@@ -263,9 +256,9 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(Settings) {
|
||||
REFLECT(symbol_grid);
|
||||
REFLECT(symbol_grid_snap);
|
||||
REFLECT(default_game);
|
||||
REFLECT(print_layout);
|
||||
REFLECT(apprentice_location);
|
||||
REFLECT(internal_scale);
|
||||
REFLECT(print_spacing);
|
||||
REFLECT(apprentice_location);
|
||||
REFLECT(internal_scale);
|
||||
REFLECT(internal_image_extension);
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
REFLECT(updates_url);
|
||||
|
||||
@@ -112,13 +112,6 @@ public:
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Printing settings
|
||||
|
||||
enum PageLayoutType
|
||||
{ LAYOUT_NO_SPACE
|
||||
, LAYOUT_EQUAL_SPACE
|
||||
//, LAYOUT_CUSTOM
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Settings
|
||||
|
||||
@@ -188,7 +181,7 @@ public:
|
||||
|
||||
// --------------------------------------------------- : Printing
|
||||
|
||||
PageLayoutType print_layout;
|
||||
double print_spacing;
|
||||
|
||||
// --------------------------------------------------- : Special game stuff
|
||||
String apprentice_location;
|
||||
|
||||
+171
-80
@@ -14,40 +14,149 @@
|
||||
#include <data/card.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <render/card/viewer.hpp>
|
||||
#include <wx/print.h>
|
||||
#include <wx/print.h>
|
||||
#include <wx/valnum.h>
|
||||
#include <unordered_set>
|
||||
|
||||
DECLARE_POINTER_TYPE(PageLayout);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Layout
|
||||
|
||||
PageLayout::PageLayout()
|
||||
: margin_left(0), margin_right(0), margin_top(0), margin_bottom(0)
|
||||
, rows(0), cols(0), card_landscape(false)
|
||||
{}
|
||||
|
||||
void PageLayout::init(const StyleSheet& stylesheet, PageLayoutType type, const RealSize& page_size) {
|
||||
this->page_size = page_size;
|
||||
margin_left = margin_right = margin_top = margin_bottom = 0;
|
||||
card_size.width = stylesheet.card_width * 25.4 / stylesheet.card_dpi;
|
||||
card_size.height = stylesheet.card_height * 25.4 / stylesheet.card_dpi;
|
||||
card_dpi = stylesheet.card_dpi;
|
||||
card_landscape = card_size.width > card_size.height;
|
||||
cols = int(floor(page_size.width / card_size.width));
|
||||
rows = int(floor(page_size.height / card_size.height));
|
||||
// spacing
|
||||
double hspace = (page_size.width - (cols * card_size.width ));
|
||||
double vspace = (page_size.height - (rows * card_size.height));
|
||||
if (type == LAYOUT_NO_SPACE) {
|
||||
// no space between cards
|
||||
card_spacing.width = card_spacing.height = 0;
|
||||
margin_left = margin_right = hspace / 2;
|
||||
margin_top = vspace * 1./3; margin_bottom = vspace * 2./3; // most printers have more margin at the bottom
|
||||
} else {
|
||||
// distribute whitespace evenly
|
||||
margin_left = margin_right = card_spacing.width = hspace / (cols + 1);
|
||||
margin_top = margin_bottom = card_spacing.height = vspace / (rows + 1);
|
||||
}
|
||||
void PrintJob::init(const RealSize& page_size) {
|
||||
this->page_size = page_size;
|
||||
if (cards.empty()) return;
|
||||
measure_cards();
|
||||
layout_cards();
|
||||
align_cards();
|
||||
center_cards();
|
||||
}
|
||||
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);
|
||||
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
|
||||
if (rotated) {
|
||||
swap(size.width, size.height);
|
||||
rotation = rad90;
|
||||
}
|
||||
CardLayout layout(card, size, rotation);
|
||||
card_layouts.push_back(layout);
|
||||
}
|
||||
std::sort(card_layouts.begin(), card_layouts.end());
|
||||
}
|
||||
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;
|
||||
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;
|
||||
// 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.width + spacing;
|
||||
row_height = max(row_height, card_layouts[i].size.height + spacing);
|
||||
goto continue_outer;
|
||||
}
|
||||
// no card fits
|
||||
if (row_top == 0.0 && row_height == 0.0 && row_width == 0.0) {
|
||||
// none of the remaining cards can fit on an empty page, return
|
||||
page_layouts.pop_back();
|
||||
queue_message(MESSAGE_WARNING, _ERROR_("cards bigger than page"));
|
||||
return;
|
||||
}
|
||||
if (row_height == 0.0 && row_width == 0.0) {
|
||||
// none of the remaining cards can fit on an empty row, create a new page
|
||||
page_layouts.push_back(vector<CardLayout>());
|
||||
row_top = 0.0;
|
||||
continue;
|
||||
}
|
||||
// none of the remaining cards can fit on this row, create a new row
|
||||
row_top += row_height;
|
||||
row_width = row_height = 0.0;
|
||||
continue_outer:;
|
||||
}
|
||||
}
|
||||
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];
|
||||
// for each card on the page
|
||||
for (int max = 0, j = 0; j < page_layout.size(); ++max, ++j) {
|
||||
if (max > 100) {
|
||||
queue_message(MESSAGE_WARNING, _("DEBUG: large amount of iterations when aligning cards for print"));
|
||||
break;
|
||||
}
|
||||
double x = page_layout[j].pos.width;
|
||||
double y = page_layout[j].pos.height;
|
||||
// if another card is almost aligned
|
||||
for (int i = 0; i < page_layout.size(); ++i) {
|
||||
if (i == j) continue;
|
||||
double difference = page_layout[i].pos.width - x;
|
||||
if (threshold_bottom < difference && difference <= threshold_top) {
|
||||
// get the card, and all cards to the right on the same row
|
||||
vector<int> cards;
|
||||
cards.push_back(j);
|
||||
for (int h = 0; h < page_layout.size(); ++h) {
|
||||
if (h == j) continue;
|
||||
double difference_x = page_layout[h].pos.width - x;
|
||||
double difference_y = abs(page_layout[h].pos.height - y);
|
||||
if (difference_y < threshold_bottom && difference_x > threshold_bottom) {
|
||||
cards.push_back(h);
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
can_move = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// move the cards
|
||||
if (can_move) {
|
||||
for (int h = 0; h < cards.size(); ++h) {
|
||||
page_layout[cards[h]].pos.width += difference;
|
||||
}
|
||||
j = -1; // restart, new cards may be in range now
|
||||
goto continue_outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue_outer:;
|
||||
}
|
||||
}
|
||||
}
|
||||
void PrintJob::center_cards() {
|
||||
for (int p = 0; p < page_layouts.size(); ++p) {
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Printout
|
||||
|
||||
@@ -70,11 +179,11 @@ private:
|
||||
double scale_x, scale_y; // priter pixel per mm
|
||||
|
||||
int pageCount() {
|
||||
return job->num_pages();
|
||||
return job->page_layouts.size();
|
||||
}
|
||||
|
||||
/// Draw a card, that is card_nr on this page, find the postion by asking the layout
|
||||
void drawCard(DC& dc, const CardP& card, int card_nr);
|
||||
/// Draw a card according to it's CardLayout info
|
||||
void drawCard(DC& dc, const PrintJob::CardLayout& card_layout);
|
||||
};
|
||||
|
||||
CardsPrintout::CardsPrintout(PrintJobP const& job)
|
||||
@@ -93,88 +202,69 @@ bool CardsPrintout::HasPage(int page) {
|
||||
}
|
||||
|
||||
void CardsPrintout::OnPreparePrinting() {
|
||||
if (job->layout.empty()) {
|
||||
if (job->empty()) {
|
||||
int pw_mm, ph_mm;
|
||||
GetPageSizeMM(&pw_mm, &ph_mm);
|
||||
job->layout.init(*job->set->stylesheet, job->layout_type, RealSize(pw_mm, ph_mm));
|
||||
job->init(RealSize(pw_mm, ph_mm));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CardsPrintout::OnPrintPage(int page) {
|
||||
DC& dc = *GetDC();
|
||||
// scale factors (pixels per mm)
|
||||
// page size in millimeters
|
||||
int pw_mm, ph_mm;
|
||||
GetPageSizeMM(&pw_mm, &ph_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;
|
||||
// print the cards that belong on this page
|
||||
int start = (page - 1) * job->layout.cards_per_page();
|
||||
int end = min((int)job->cards.size(), start + job->layout.cards_per_page());
|
||||
for (int i = start ; i < end ; ++i) {
|
||||
drawCard(dc, job->cards.at(i), i - start);
|
||||
// print the cards that belong on this page
|
||||
FOR_EACH(card_layout, job->page_layouts[page - 1]) {
|
||||
drawCard(dc, card_layout);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardsPrintout::drawCard(DC& dc, const CardP& card, int card_nr) {
|
||||
const StyleSheet& stylesheet = job->set->stylesheetFor(card);
|
||||
// determine dpi factor for this card
|
||||
double px_per_mm = stylesheet.card_dpi / 25.4;
|
||||
// determine position
|
||||
int col = card_nr % job->layout.cols;
|
||||
int row = card_nr / job->layout.cols;
|
||||
RealPoint pos((job->layout.margin_left + (job->layout.card_size.width + job->layout.card_spacing.width) * col) / scale_x * px_per_mm
|
||||
,(job->layout.margin_top + (job->layout.card_size.height + job->layout.card_spacing.height) * row) / scale_y * px_per_mm);
|
||||
// determine rotation
|
||||
Radians rotation = 0;
|
||||
if ((stylesheet.card_width > stylesheet.card_height) != job->layout.card_landscape) {
|
||||
rotation = rad90;
|
||||
}
|
||||
/*
|
||||
// size of this particular card (in mm)
|
||||
RealSize card_size( stylesheet.card_width * 25.4 / stylesheet.card_dpi
|
||||
, stylesheet.card_height * 25.4 / stylesheet.card_dpi);
|
||||
if (is_rad90(rotation)) swap(card_size.width, card_size.height);
|
||||
// adjust card size, to center card in the available space (from job->layout.card_size)?
|
||||
// TODO: deal with different sized cards in general
|
||||
*/
|
||||
|
||||
// create buffers
|
||||
void CardsPrintout::drawCard(DC& dc, const PrintJob::CardLayout& card_layout) {
|
||||
const StyleSheet& stylesheet = job->set->stylesheetFor(card_layout.card);
|
||||
// draw card to its own buffer
|
||||
int w = int(stylesheet.card_width), h = int(stylesheet.card_height); // in pixels
|
||||
if (is_rad90(rotation)) swap(w,h);
|
||||
// Draw using text buffer
|
||||
if (is_rad90(card_layout.rotation)) swap(w,h);
|
||||
wxBitmap buffer(w,h,32);
|
||||
wxMemoryDC bufferDC;
|
||||
bufferDC.SelectObject(buffer);
|
||||
clearDC(bufferDC,*wxWHITE_BRUSH);
|
||||
RotatedDC rdc(bufferDC, rotation, stylesheet.getCardRect(), 1.0, QUALITY_AA, ROTATION_ATTACH_TOP_LEFT);
|
||||
// render card to dc
|
||||
viewer.setCard(card);
|
||||
RotatedDC rdc(bufferDC, card_layout.rotation, stylesheet.getCardRect(), 1.0, QUALITY_AA, ROTATION_ATTACH_TOP_LEFT);
|
||||
viewer.setCard(card_layout.card);
|
||||
viewer.draw(rdc, *wxWHITE);
|
||||
// render buffer to device
|
||||
dc.SetUserScale(scale_x / px_per_mm, scale_y / px_per_mm);
|
||||
bufferDC.SelectObject(wxNullBitmap);
|
||||
dc.DrawBitmap(buffer, int(scale_x * pos.x), int(scale_y * pos.y));
|
||||
// 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));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PrintWindow
|
||||
|
||||
PrintJobP make_print_job(Window* parent, const SetP& set, const ExportCardSelectionChoices& choices) {
|
||||
// Let the user choose cards
|
||||
// Let the user choose cards and spacing
|
||||
// controls
|
||||
ExportWindowBase wnd(parent, _TITLE_("select cards print"), set, choices);
|
||||
wxCheckBox* space = new wxCheckBox(&wnd, wxID_ANY, _LABEL_("put space between cards"));
|
||||
space->SetValue(settings.print_layout);
|
||||
ExportWindowBase wnd(parent, _TITLE_("select cards print"), set, choices);
|
||||
wxFloatingPointValidator<float> validator(2, NULL, wxNUM_VAL_ZERO_AS_BLANK);
|
||||
validator.SetRange(0, 100);
|
||||
wxTextCtrl* space = new wxTextCtrl(&wnd, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, 0L, validator);
|
||||
space->SetValue(wxString::Format(wxT("%lf"), settings.print_spacing));
|
||||
// layout
|
||||
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxSizer* s3 = wnd.Create();
|
||||
s2->Add(s3, 1, wxEXPAND | wxALL, 8);
|
||||
wxSizer* s4 = new wxStaticBoxSizer(wxVERTICAL, &wnd, _TITLE_("settings"));
|
||||
s4->Add(space, 1, wxALL | wxALIGN_TOP, 8);
|
||||
s4->Add(new wxStaticText(&wnd, -1, _LABEL_("spacing print")), 0, wxALL, 8);
|
||||
s4->Add(space, 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);
|
||||
@@ -185,10 +275,11 @@ PrintJobP make_print_job(Window* parent, const SetP& set, const ExportCardSelect
|
||||
if (wnd.ShowModal() != wxID_OK) {
|
||||
return PrintJobP(); // cancel
|
||||
} else {
|
||||
// make print job
|
||||
PrintJobP job = make_intrusive<PrintJob>(set);
|
||||
job->layout_type = settings.print_layout = space->GetValue() ? LAYOUT_EQUAL_SPACE : LAYOUT_NO_SPACE;
|
||||
job->cards = wnd.getSelection();
|
||||
// make print job
|
||||
double spacing;
|
||||
space->GetValue().ToDouble(&spacing);
|
||||
settings.print_spacing = spacing;
|
||||
PrintJobP job = make_intrusive<PrintJob>(set, wnd.getSelection(), spacing);
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
+45
-37
@@ -11,52 +11,60 @@
|
||||
#include <util/prec.hpp>
|
||||
#include <util/reflect.hpp>
|
||||
#include <util/real_point.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <gui/card_select_window.hpp>
|
||||
#include <data/set.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
|
||||
DECLARE_POINTER_TYPE(Set);
|
||||
DECLARE_POINTER_TYPE(PrintJob);
|
||||
class StyleSheet;
|
||||
|
||||
// ----------------------------------------------------------------------------- : Layout
|
||||
|
||||
/// Layout of a page of cards
|
||||
class PageLayout {
|
||||
public:
|
||||
// layout
|
||||
RealSize page_size; ///< Size of a page (in millimetres)
|
||||
RealSize card_size; ///< Size of a card (in millimetres)
|
||||
RealSize card_spacing; ///< Spacing between cards (in millimetres)
|
||||
double card_dpi; ///< Dots per inch of the default stylesheet
|
||||
double margin_left, margin_right, margin_top, margin_bottom; ///< Page margins (in millimetres)
|
||||
int rows, cols; ///< Number of rows/columns of cards
|
||||
bool card_landscape; ///< Are cards rotated to landscape orientation?
|
||||
|
||||
PageLayout();
|
||||
void init(const StyleSheet& stylesheet, PageLayoutType layout_type, const RealSize& page_size);
|
||||
|
||||
/// Is this layout uninitialized?
|
||||
inline bool empty() const { return cards_per_page() == 0; }
|
||||
/// The number of cards per page
|
||||
inline int cards_per_page() const { return rows * cols; }
|
||||
};
|
||||
// ----------------------------------------------------------------------------- : Job
|
||||
|
||||
class PrintJob : public IntrusivePtrBase<PrintJob> {
|
||||
public:
|
||||
PrintJob(SetP const& set) : set(set) {}
|
||||
|
||||
// set and cards to print
|
||||
SetP set;
|
||||
vector<CardP> cards;
|
||||
|
||||
// printing options
|
||||
PageLayoutType layout_type;
|
||||
PageLayout layout;
|
||||
|
||||
inline int num_pages() const {
|
||||
int cards_per_page = max(1,layout.cards_per_page());
|
||||
return ((int)cards.size() + cards_per_page - 1) / cards_per_page;
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
struct CardLayout {
|
||||
CardLayout(const CardP& card, const RealSize& size, Radians rotation)
|
||||
: card(card), size(size), rotation(rotation) {}
|
||||
|
||||
bool operator<(const CardLayout& that) const {
|
||||
return size.width > that.size.width; // put the widest cards first
|
||||
}
|
||||
|
||||
CardP card;
|
||||
RealSize size;
|
||||
Radians rotation;
|
||||
|
||||
RealSize pos;
|
||||
};
|
||||
|
||||
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<vector<CardLayout>> page_layouts; ///< The CardLayout grouped by page
|
||||
|
||||
/// Is this job uninitialized?
|
||||
inline bool empty() const { return page_layouts.empty(); }
|
||||
|
||||
private:
|
||||
// calculate the width and height of each card in millimeters
|
||||
void measure_cards();
|
||||
// calculate where the cards go on the pages
|
||||
void layout_cards();
|
||||
// if two cards are almost aligned, align them
|
||||
void align_cards();
|
||||
// center the cards on the middle of each page
|
||||
void center_cards();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Printing
|
||||
|
||||
Reference in New Issue
Block a user