mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-12 05:36:59 -04:00
Change tabs to two spaces.
This commit is contained in:
+544
-544
File diff suppressed because it is too large
Load Diff
@@ -23,131 +23,131 @@
|
||||
/// Serialize an object to a string, clipboard_package will be set to the given package.
|
||||
template <typename T>
|
||||
String serialize_for_clipboard(Package& package, T& object) {
|
||||
shared_ptr<wxStringOutputStream> stream( new wxStringOutputStream );
|
||||
Writer writer(stream, file_version_clipboard);
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
writer.handle(object);
|
||||
return stream->GetString();
|
||||
shared_ptr<wxStringOutputStream> stream( new wxStringOutputStream );
|
||||
Writer writer(stream, file_version_clipboard);
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
writer.handle(object);
|
||||
return stream->GetString();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void deserialize_from_clipboard(T& object, Package& package, const String& data) {
|
||||
shared_ptr<wxStringInputStream> stream( new wxStringInputStream(data) );
|
||||
Reader reader(stream, nullptr, _("clipboard"));
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
reader.handle_greedy(object);
|
||||
shared_ptr<wxStringInputStream> stream( new wxStringInputStream(data) );
|
||||
Reader reader(stream, nullptr, _("clipboard"));
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
reader.handle_greedy(object);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : CardDataObject
|
||||
|
||||
/// A wrapped cards for storing on the clipboard
|
||||
struct WrappedCards {
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
vector<CardP> cards;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
vector<CardP> cards;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION(WrappedCards) {
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(cards);
|
||||
}
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(cards);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxDataFormat CardsDataObject::format = _("application/x-mse-cards");
|
||||
|
||||
CardsDataObject::CardsDataObject(const SetP& set, const vector<CardP>& cards) {
|
||||
// set the stylesheet, so when deserializing we know whos style options we are reading
|
||||
bool* has_styling = new bool[cards.size()];
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
has_styling[i] = cards[i]->has_styling && !cards[i]->stylesheet;
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = set->stylesheet;
|
||||
}
|
||||
}
|
||||
WrappedCards data = { set->game.get(), set->game->name(), cards };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
// restore cards
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = StyleSheetP();
|
||||
}
|
||||
}
|
||||
SetFormat(format);
|
||||
delete [] has_styling;
|
||||
// set the stylesheet, so when deserializing we know whos style options we are reading
|
||||
bool* has_styling = new bool[cards.size()];
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
has_styling[i] = cards[i]->has_styling && !cards[i]->stylesheet;
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = set->stylesheet;
|
||||
}
|
||||
}
|
||||
WrappedCards data = { set->game.get(), set->game->name(), cards };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
// restore cards
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = StyleSheetP();
|
||||
}
|
||||
}
|
||||
SetFormat(format);
|
||||
delete [] has_styling;
|
||||
}
|
||||
|
||||
CardsDataObject::CardsDataObject() {
|
||||
SetFormat(format);
|
||||
SetFormat(format);
|
||||
}
|
||||
|
||||
bool CardsDataObject::getCards(const SetP& set, vector<CardP>& out) {
|
||||
WrappedCards data = { set->game.get(), set->game->name() };
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.cards.empty()) return false;
|
||||
if (data.game_name == set->game->name()) {
|
||||
// Cards are from the same game
|
||||
out = data.cards;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
WrappedCards data = { set->game.get(), set->game->name() };
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.cards.empty()) return false;
|
||||
if (data.game_name == set->game->name()) {
|
||||
// Cards are from the same game
|
||||
out = data.cards;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : KeywordDataObject
|
||||
|
||||
/// A wrapped keyword for storing on the clipboard
|
||||
struct WrappedKeyword {
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
KeywordP keyword;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
KeywordP keyword;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION(WrappedKeyword) {
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(keyword);
|
||||
}
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxDataFormat KeywordDataObject::format = _("application/x-mse-keyword");
|
||||
|
||||
KeywordDataObject::KeywordDataObject(const SetP& set, const KeywordP& keyword) {
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
SetFormat(format);
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
SetFormat(format);
|
||||
}
|
||||
|
||||
KeywordDataObject::KeywordDataObject() {
|
||||
SetFormat(format);
|
||||
SetFormat(format);
|
||||
}
|
||||
|
||||
KeywordP KeywordDataObject::getKeyword(const SetP& set) {
|
||||
KeywordP keyword(new Keyword());
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword};
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.game_name != set->game->name()) return KeywordP(); // Keyword is from a different game
|
||||
else return keyword;
|
||||
KeywordP keyword(new Keyword());
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword};
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.game_name != set->game->name()) return KeywordP(); // Keyword is from a different game
|
||||
else return keyword;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Card on clipboard
|
||||
|
||||
CardsOnClipboard::CardsOnClipboard(const SetP& set, const vector<CardP>& cards) {
|
||||
// Conversion to text format
|
||||
// TODO
|
||||
//Add( new TextDataObject(_("card")))
|
||||
// Conversion to bitmap format
|
||||
if (cards.size() == 1) {
|
||||
Add(new wxBitmapDataObject(export_bitmap(set, cards[0])));
|
||||
}
|
||||
// Conversion to serialized card format
|
||||
Add(new CardsDataObject(set, cards), true);
|
||||
// Conversion to text format
|
||||
// TODO
|
||||
//Add( new TextDataObject(_("card")))
|
||||
// Conversion to bitmap format
|
||||
if (cards.size() == 1) {
|
||||
Add(new wxBitmapDataObject(export_bitmap(set, cards[0])));
|
||||
}
|
||||
// Conversion to serialized card format
|
||||
Add(new CardsDataObject(set, cards), true);
|
||||
}
|
||||
|
||||
@@ -21,16 +21,16 @@ DECLARE_POINTER_TYPE(Keyword);
|
||||
/// The data format for cards on the clipboard
|
||||
class CardsDataObject : public wxTextDataObject {
|
||||
public:
|
||||
/// Name of the format of MSE cards
|
||||
static wxDataFormat format;
|
||||
|
||||
CardsDataObject();
|
||||
/// Store a card
|
||||
CardsDataObject(const SetP& set, const vector<CardP>& cards);
|
||||
|
||||
/// Retrieve the cards, only if it is made with the same game as set
|
||||
/** Return true if the cards are correctly retrieved, and there is at least one card */
|
||||
bool getCards(const SetP& set, vector<CardP>& out);
|
||||
/// Name of the format of MSE cards
|
||||
static wxDataFormat format;
|
||||
|
||||
CardsDataObject();
|
||||
/// Store a card
|
||||
CardsDataObject(const SetP& set, const vector<CardP>& cards);
|
||||
|
||||
/// Retrieve the cards, only if it is made with the same game as set
|
||||
/** Return true if the cards are correctly retrieved, and there is at least one card */
|
||||
bool getCards(const SetP& set, vector<CardP>& out);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : KeywordDataObject
|
||||
@@ -38,15 +38,15 @@ class CardsDataObject : public wxTextDataObject {
|
||||
/// The data format for keywords on the clipboard
|
||||
class KeywordDataObject : public wxTextDataObject {
|
||||
public:
|
||||
/// Name of the format of MSE keywords
|
||||
static wxDataFormat format;
|
||||
|
||||
KeywordDataObject();
|
||||
/// Store a keyword
|
||||
KeywordDataObject(const SetP& set, const KeywordP& card);
|
||||
|
||||
/// Retrieve a keyword, only if it is made with the same game as set
|
||||
KeywordP getKeyword(const SetP& set);
|
||||
/// Name of the format of MSE keywords
|
||||
static wxDataFormat format;
|
||||
|
||||
KeywordDataObject();
|
||||
/// Store a keyword
|
||||
KeywordDataObject(const SetP& set, const KeywordP& card);
|
||||
|
||||
/// Retrieve a keyword, only if it is made with the same game as set
|
||||
KeywordP getKeyword(const SetP& set);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Card on clipboard
|
||||
@@ -54,7 +54,7 @@ class KeywordDataObject : public wxTextDataObject {
|
||||
/// A DataObject for putting one or more cards on the clipboard, in multiple formats
|
||||
class CardsOnClipboard : public wxDataObjectComposite {
|
||||
public:
|
||||
CardsOnClipboard(const SetP& set, const vector<CardP>& cards);
|
||||
CardsOnClipboard(const SetP& set, const vector<CardP>& cards);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+37
-37
@@ -19,53 +19,53 @@ DECLARE_TYPEOF_COLLECTION(FileFormatP);
|
||||
vector<FileFormatP> file_formats;
|
||||
|
||||
void init_file_formats() {
|
||||
file_formats.push_back(mse2_file_format());
|
||||
file_formats.push_back(mse1_file_format());
|
||||
file_formats.push_back(mtg_editor_file_format());
|
||||
file_formats.push_back(mse2_file_format());
|
||||
file_formats.push_back(mse1_file_format());
|
||||
file_formats.push_back(mtg_editor_file_format());
|
||||
}
|
||||
|
||||
String import_formats() {
|
||||
String all_extensions; // type1;type2
|
||||
String type_strings; // |name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canImport()) {
|
||||
if (!all_extensions.empty()) all_extensions += _(";");
|
||||
all_extensions += f->matches();
|
||||
type_strings += _("|") + f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return _("Set files|") + all_extensions + type_strings + _("|All files (*.*)|*");
|
||||
String all_extensions; // type1;type2
|
||||
String type_strings; // |name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canImport()) {
|
||||
if (!all_extensions.empty()) all_extensions += _(";");
|
||||
all_extensions += f->matches();
|
||||
type_strings += _("|") + f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return _("Set files|") + all_extensions + type_strings + _("|All files (*.*)|*");
|
||||
}
|
||||
|
||||
String export_formats(const Game& game) {
|
||||
String type_strings; // name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canExport(game)) {
|
||||
if (!type_strings.empty()) type_strings += _("|");
|
||||
type_strings += f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return type_strings;
|
||||
String type_strings; // name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canExport(game)) {
|
||||
if (!type_strings.empty()) type_strings += _("|");
|
||||
type_strings += f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return type_strings;
|
||||
}
|
||||
|
||||
void export_set(Set& set, const String& filename, size_t format_index, bool is_copy) {
|
||||
FileFormatP format = file_formats.at(format_index);
|
||||
if (!format->canExport(*set.game)) {
|
||||
throw InternalError(_("File format doesn't apply to set"));
|
||||
}
|
||||
format->exportSet(set, filename, is_copy);
|
||||
FileFormatP format = file_formats.at(format_index);
|
||||
if (!format->canExport(*set.game)) {
|
||||
throw InternalError(_("File format doesn't apply to set"));
|
||||
}
|
||||
format->exportSet(set, filename, is_copy);
|
||||
}
|
||||
|
||||
SetP import_set(String name) {
|
||||
size_t pos = name.find_last_of(_('.'));
|
||||
String extension = pos==String::npos ? _("") : name.substr(pos + 1);
|
||||
// determine format
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->extension() == extension) {
|
||||
return f->importSet(name);
|
||||
}
|
||||
}
|
||||
// default: use first format = MSE2 format
|
||||
assert(!file_formats.empty() && file_formats[0]->canImport());
|
||||
return file_formats[0]->importSet(name);
|
||||
size_t pos = name.find_last_of(_('.'));
|
||||
String extension = pos==String::npos ? _("") : name.substr(pos + 1);
|
||||
// determine format
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->extension() == extension) {
|
||||
return f->importSet(name);
|
||||
}
|
||||
}
|
||||
// default: use first format = MSE2 format
|
||||
assert(!file_formats.empty() && file_formats[0]->canImport());
|
||||
return file_formats[0]->importSet(name);
|
||||
}
|
||||
|
||||
+22
-22
@@ -23,28 +23,28 @@ DECLARE_POINTER_TYPE(FileFormat);
|
||||
/// A filter for a specific file format
|
||||
class FileFormat : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
virtual ~FileFormat() {}
|
||||
/// File extension used by this file format
|
||||
virtual String extension() = 0;
|
||||
/// What to match against
|
||||
virtual String matches() {
|
||||
return _("*.") + extension();
|
||||
}
|
||||
/// Name of the filter
|
||||
virtual String name() = 0;
|
||||
/// Can it be used for importing sets?
|
||||
virtual bool canImport() = 0;
|
||||
/// Can it be used for exporting sets for a particular game?
|
||||
virtual bool canExport(const Game&) = 0;
|
||||
/// Import using this filter
|
||||
virtual SetP importSet(const String& filename) {
|
||||
throw InternalError(_("Import not supported by this file format"));
|
||||
}
|
||||
/// Export using this filter
|
||||
/** If is_copy, then the set should not be modified */
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy = false) {
|
||||
throw InternalError(_("Export not supported by this file format"));
|
||||
}
|
||||
virtual ~FileFormat() {}
|
||||
/// File extension used by this file format
|
||||
virtual String extension() = 0;
|
||||
/// What to match against
|
||||
virtual String matches() {
|
||||
return _("*.") + extension();
|
||||
}
|
||||
/// Name of the filter
|
||||
virtual String name() = 0;
|
||||
/// Can it be used for importing sets?
|
||||
virtual bool canImport() = 0;
|
||||
/// Can it be used for exporting sets for a particular game?
|
||||
virtual bool canExport(const Game&) = 0;
|
||||
/// Import using this filter
|
||||
virtual SetP importSet(const String& filename) {
|
||||
throw InternalError(_("Import not supported by this file format"));
|
||||
}
|
||||
/// Export using this filter
|
||||
/** If is_copy, then the set should not be modified */
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy = false) {
|
||||
throw InternalError(_("Export not supported by this file format"));
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Formats
|
||||
|
||||
+51
-51
@@ -21,46 +21,46 @@ DECLARE_TYPEOF_COLLECTION(CardP);
|
||||
// ----------------------------------------------------------------------------- : Single card export
|
||||
|
||||
void export_image(const SetP& set, const CardP& card, const String& filename) {
|
||||
Image img = export_bitmap(set, card).ConvertToImage();
|
||||
img.SaveFile(filename); // can't use Bitmap::saveFile, it wants to know the file type
|
||||
// but image.saveFile determines it automagicly
|
||||
Image img = export_bitmap(set, card).ConvertToImage();
|
||||
img.SaveFile(filename); // can't use Bitmap::saveFile, it wants to know the file type
|
||||
// but image.saveFile determines it automagicly
|
||||
}
|
||||
|
||||
class UnzoomedDataViewer : public DataViewer {
|
||||
public:
|
||||
UnzoomedDataViewer(bool use_zoom_settings)
|
||||
: use_zoom_settings(use_zoom_settings)
|
||||
{}
|
||||
virtual Rotation getRotation() const;
|
||||
UnzoomedDataViewer(bool use_zoom_settings)
|
||||
: use_zoom_settings(use_zoom_settings)
|
||||
{}
|
||||
virtual Rotation getRotation() const;
|
||||
private:
|
||||
bool use_zoom_settings;
|
||||
bool use_zoom_settings;
|
||||
};
|
||||
Rotation UnzoomedDataViewer::getRotation() const {
|
||||
if (use_zoom_settings) {
|
||||
return DataViewer::getRotation();
|
||||
} else {
|
||||
if (!stylesheet) stylesheet = set->stylesheet;
|
||||
return Rotation(0, stylesheet->getCardRect(), 1.0, 1.0, ROTATION_ATTACH_TOP_LEFT);
|
||||
}
|
||||
if (use_zoom_settings) {
|
||||
return DataViewer::getRotation();
|
||||
} else {
|
||||
if (!stylesheet) stylesheet = set->stylesheet;
|
||||
return Rotation(0, stylesheet->getCardRect(), 1.0, 1.0, ROTATION_ATTACH_TOP_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap export_bitmap(const SetP& set, const CardP& card) {
|
||||
if (!set) throw Error(_("no set"));
|
||||
// create viewer
|
||||
UnzoomedDataViewer viewer(!settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export());
|
||||
viewer.setSet(set);
|
||||
viewer.setCard(card);
|
||||
// size of cards
|
||||
RealSize size = viewer.getRotation().getExternalSize();
|
||||
// create bitmap & dc
|
||||
Bitmap bitmap((int) size.width, (int) size.height);
|
||||
if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap"));
|
||||
wxMemoryDC dc;
|
||||
dc.SelectObject(bitmap);
|
||||
// draw
|
||||
viewer.draw(dc);
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bitmap;
|
||||
if (!set) throw Error(_("no set"));
|
||||
// create viewer
|
||||
UnzoomedDataViewer viewer(!settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export());
|
||||
viewer.setSet(set);
|
||||
viewer.setCard(card);
|
||||
// size of cards
|
||||
RealSize size = viewer.getRotation().getExternalSize();
|
||||
// create bitmap & dc
|
||||
Bitmap bitmap((int) size.width, (int) size.height);
|
||||
if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap"));
|
||||
wxMemoryDC dc;
|
||||
dc.SelectObject(bitmap);
|
||||
// draw
|
||||
viewer.draw(dc);
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Multiple card export
|
||||
@@ -69,25 +69,25 @@ Bitmap export_bitmap(const SetP& set, const CardP& card) {
|
||||
void export_images(const SetP& set, const vector<CardP>& cards,
|
||||
const String& path, const String& filename_template, FilenameConflicts conflicts)
|
||||
{
|
||||
wxBusyCursor busy;
|
||||
// Script
|
||||
ScriptP filename_script = parse(filename_template, nullptr, true);
|
||||
// Path
|
||||
wxFileName fn(path);
|
||||
// Export
|
||||
std::set<String> used; // for CONFLICT_NUMBER_OVERWRITE
|
||||
FOR_EACH_CONST(card, cards) {
|
||||
// filename for this card
|
||||
Context& ctx = set->getContext(card);
|
||||
String filename = clean_filename(untag(ctx.eval(*filename_script)->toString()));
|
||||
if (!filename) continue; // no filename -> no saving
|
||||
// full path
|
||||
fn.SetFullName(filename);
|
||||
// does the file exist?
|
||||
if (!resolve_filename_conflicts(fn, conflicts, used)) continue;
|
||||
// write image
|
||||
filename = fn.GetFullPath();
|
||||
used.insert(filename);
|
||||
export_image(set, card, filename);
|
||||
}
|
||||
wxBusyCursor busy;
|
||||
// Script
|
||||
ScriptP filename_script = parse(filename_template, nullptr, true);
|
||||
// Path
|
||||
wxFileName fn(path);
|
||||
// Export
|
||||
std::set<String> used; // for CONFLICT_NUMBER_OVERWRITE
|
||||
FOR_EACH_CONST(card, cards) {
|
||||
// filename for this card
|
||||
Context& ctx = set->getContext(card);
|
||||
String filename = clean_filename(untag(ctx.eval(*filename_script)->toString()));
|
||||
if (!filename) continue; // no filename -> no saving
|
||||
// full path
|
||||
fn.SetFullName(filename);
|
||||
// does the file exist?
|
||||
if (!resolve_filename_conflicts(fn, conflicts, used)) continue;
|
||||
// write image
|
||||
filename = fn.GetFullPath();
|
||||
used.insert(filename);
|
||||
export_image(set, card, filename);
|
||||
}
|
||||
}
|
||||
|
||||
+350
-350
@@ -7,10 +7,10 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
// VS 8 has the audacity to give a warning about fill_n
|
||||
// That is both STUPID and WRONG, so disable that warning
|
||||
// This has to be done before includes, because the warning is reported in standard headers!
|
||||
#pragma warning(disable:4996)
|
||||
// VS 8 has the audacity to give a warning about fill_n
|
||||
// That is both STUPID and WRONG, so disable that warning
|
||||
// This has to be done before includes, because the warning is reported in standard headers!
|
||||
#pragma warning(disable:4996)
|
||||
#endif
|
||||
|
||||
#include <util/prec.hpp>
|
||||
@@ -25,9 +25,9 @@ DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
// ----------------------------------------------------------------------------- : Image preprocessing
|
||||
|
||||
enum ImageMarker
|
||||
{ EMPTY = 0 // This cell is empty
|
||||
, FULL = 1 // This cell is full
|
||||
, MARKED = 2 // This cell is full, but it has been used as a starting point for finding symbols
|
||||
{ EMPTY = 0 // This cell is empty
|
||||
, FULL = 1 // This cell is full
|
||||
, MARKED = 2 // This cell is full, but it has been used as a starting point for finding symbols
|
||||
};
|
||||
|
||||
|
||||
@@ -37,13 +37,13 @@ enum ImageMarker
|
||||
* is no longer an actual image.
|
||||
*/
|
||||
void greyscale(Image& img) {
|
||||
UInt size = img.GetWidth() * img.GetHeight();
|
||||
Byte* data = img.GetData();
|
||||
Byte* out = data;
|
||||
for (UInt i = 0 ; i < size ; ++i) {
|
||||
*out++ = (data[0] + data[1] + data[2]) / 3;
|
||||
data += 3;
|
||||
}
|
||||
UInt size = img.GetWidth() * img.GetHeight();
|
||||
Byte* data = img.GetData();
|
||||
Byte* out = data;
|
||||
for (UInt i = 0 ; i < size ; ++i) {
|
||||
*out++ = (data[0] + data[1] + data[2]) / 3;
|
||||
data += 3;
|
||||
}
|
||||
}
|
||||
|
||||
/// Thresholds an image, giving a black & white result
|
||||
@@ -52,192 +52,192 @@ void greyscale(Image& img) {
|
||||
* EMPTY for the 'border' color, FULL for the interior
|
||||
*/
|
||||
void threshold(Byte* data, int w, int h) {
|
||||
size_t size = w * h;
|
||||
// make histogram of data
|
||||
size_t hist[256];
|
||||
fill_n(hist,256,0);
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
hist[data[i]]++;
|
||||
}
|
||||
// find threshold
|
||||
size_t threshold_pos = size / 2;
|
||||
int threshold = 255;
|
||||
size_t below = 0;
|
||||
for (int i = 0 ; i < 255 ; ++i) {
|
||||
if (below + hist[i]/2 > threshold_pos) {
|
||||
threshold = i;
|
||||
break;
|
||||
}
|
||||
below += hist[i];
|
||||
if (below >= threshold_pos) {
|
||||
threshold = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// threshold data
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] >= threshold ? FULL : EMPTY;
|
||||
}
|
||||
// should the colors be inverted?
|
||||
int border_count = 0;
|
||||
for (int x = 0 ; x < w ; ++x) {
|
||||
border_count += data[x] + data[x+(h-1)*w];
|
||||
}
|
||||
for (int y = 0 ; y < h ; ++y) {
|
||||
border_count += data[y*w] + data[w-1+y*w];
|
||||
}
|
||||
if (border_count > w + h) {
|
||||
// more then half the border if FULL, invert
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] == FULL ? EMPTY : FULL;
|
||||
}
|
||||
}
|
||||
size_t size = w * h;
|
||||
// make histogram of data
|
||||
size_t hist[256];
|
||||
fill_n(hist,256,0);
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
hist[data[i]]++;
|
||||
}
|
||||
// find threshold
|
||||
size_t threshold_pos = size / 2;
|
||||
int threshold = 255;
|
||||
size_t below = 0;
|
||||
for (int i = 0 ; i < 255 ; ++i) {
|
||||
if (below + hist[i]/2 > threshold_pos) {
|
||||
threshold = i;
|
||||
break;
|
||||
}
|
||||
below += hist[i];
|
||||
if (below >= threshold_pos) {
|
||||
threshold = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// threshold data
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] >= threshold ? FULL : EMPTY;
|
||||
}
|
||||
// should the colors be inverted?
|
||||
int border_count = 0;
|
||||
for (int x = 0 ; x < w ; ++x) {
|
||||
border_count += data[x] + data[x+(h-1)*w];
|
||||
}
|
||||
for (int y = 0 ; y < h ; ++y) {
|
||||
border_count += data[y*w] + data[w-1+y*w];
|
||||
}
|
||||
if (border_count > w + h) {
|
||||
// more then half the border if FULL, invert
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] == FULL ? EMPTY : FULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Image to symbol
|
||||
|
||||
bool is_mse1_symbol(const Image& img) {
|
||||
// mse1 symbols are 60x80
|
||||
if (img.GetWidth() != 60 || img.GetHeight() != 80) return false;
|
||||
// the right side is black & white
|
||||
int delta = 0;
|
||||
for (int y = 0 ; y < 80 ; ++y) {
|
||||
Byte* d = img.GetData() + 3 * (y * 60 + 20);
|
||||
for (int x = 20 ; x < 60 ; ++x) {
|
||||
int r = *d++;
|
||||
int g = *d++;
|
||||
int b = *d++;
|
||||
delta += abs(r - b) + abs(r - g) + abs(b - g);
|
||||
}
|
||||
}
|
||||
if (delta > 5000) return false; // not black & white enough
|
||||
// TODO : more checks?
|
||||
return true;
|
||||
// mse1 symbols are 60x80
|
||||
if (img.GetWidth() != 60 || img.GetHeight() != 80) return false;
|
||||
// the right side is black & white
|
||||
int delta = 0;
|
||||
for (int y = 0 ; y < 80 ; ++y) {
|
||||
Byte* d = img.GetData() + 3 * (y * 60 + 20);
|
||||
for (int x = 20 ; x < 60 ; ++x) {
|
||||
int r = *d++;
|
||||
int g = *d++;
|
||||
int b = *d++;
|
||||
delta += abs(r - b) + abs(r - g) + abs(b - g);
|
||||
}
|
||||
}
|
||||
if (delta > 5000) return false; // not black & white enough
|
||||
// TODO : more checks?
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ImageData {
|
||||
int width, height;
|
||||
Byte* data;
|
||||
mutable Byte dummy;
|
||||
inline Byte& operator () (int x, int y) const {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return (dummy = EMPTY); // outside, return empty
|
||||
} else {
|
||||
return data[x + y*width];
|
||||
}
|
||||
}
|
||||
int width, height;
|
||||
Byte* data;
|
||||
mutable Byte dummy;
|
||||
inline Byte& operator () (int x, int y) const {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return (dummy = EMPTY); // outside, return empty
|
||||
} else {
|
||||
return data[x + y*width];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool find_symbol_shape_start(const ImageData& data, int& x_out, int& y_out) {
|
||||
for (int x = 0 ; x < data.width ; ++x) {
|
||||
for (int y = 0 ; y < data.height ; ++y) {
|
||||
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
|
||||
// the point above must be clear, we don't want to start in the 'ground'
|
||||
// also, we don't want to find things we found before
|
||||
x_out = x;
|
||||
y_out = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
for (int x = 0 ; x < data.width ; ++x) {
|
||||
for (int y = 0 ; y < data.height ; ++y) {
|
||||
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
|
||||
// the point above must be clear, we don't want to start in the 'ground'
|
||||
// also, we don't want to find things we found before
|
||||
x_out = x;
|
||||
y_out = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SymbolShapeP read_symbol_shape(const ImageData& data) {
|
||||
// find start point
|
||||
int xs, ys;
|
||||
if (!find_symbol_shape_start(data, xs, ys)) return SymbolShapeP();
|
||||
data(xs, ys) |= MARKED;
|
||||
|
||||
SymbolShapeP shape(new SymbolShape);
|
||||
|
||||
// walk around, clockwise
|
||||
xs += 1; // start right of the found point, otherwise last_move might think we came from above
|
||||
int x = xs, y = ys;
|
||||
int old_x = x, old_y = y;
|
||||
int last_move = 1; // 1 = right or down, (as in x|y += 1)
|
||||
do {
|
||||
// the cursor (x,y) is between four pounts:
|
||||
// a b
|
||||
// .
|
||||
// c d
|
||||
bool a = data(x-1, y-1) & FULL;
|
||||
bool b = data(x, y-1) & FULL;
|
||||
bool c = data(x-1, y ) & FULL;
|
||||
bool d = data(x, y ) & FULL;
|
||||
UInt pack = (a << 12) + (b << 8) + (c << 4) + d; // 0xabcd
|
||||
switch (pack) {
|
||||
case 0x0001 : x += 1; break;
|
||||
case 0x0010 : y += 1; break;
|
||||
case 0x0011 : x += 1; break;
|
||||
case 0x0100 : y -= 1; break;
|
||||
case 0x0101 : y -= 1; break;
|
||||
case 0x0110 : y -= last_move; break; // diagonal, we can come here from two sides, from left and right
|
||||
case 0x0111 : y -= 1; break; // last_move indicates which of {b,c} we are 'attached' to
|
||||
case 0x1000 : x -= 1; break;
|
||||
case 0x1001 : x += last_move; break;
|
||||
case 0x1010 : y += 1; break;
|
||||
case 0x1011 : x += 1; break;
|
||||
case 0x1100 : x -= 1; break;
|
||||
case 0x1101 : x -= 1; break;
|
||||
case 0x1110 : y += 1; break;
|
||||
default:
|
||||
throw InternalError(_("in the ground/air"));
|
||||
}
|
||||
|
||||
// add to shape and place a mark
|
||||
shape->points.push_back(intrusive(new ControlPoint(
|
||||
double(x) / data.width,
|
||||
double(y) / data.height
|
||||
)));
|
||||
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the shape
|
||||
last_move = (x + y) - (old_x + old_y);
|
||||
old_x = x;
|
||||
old_y = y;
|
||||
} while (x != xs || y != ys); // we will end up in the start point
|
||||
|
||||
// are we on the inside or the outside?
|
||||
if (data(x-2,y-1) & FULL) {
|
||||
shape->combine = SYMBOL_COMBINE_SUBTRACT;
|
||||
} else {
|
||||
shape->combine = SYMBOL_COMBINE_MERGE;
|
||||
}
|
||||
return shape;
|
||||
// find start point
|
||||
int xs, ys;
|
||||
if (!find_symbol_shape_start(data, xs, ys)) return SymbolShapeP();
|
||||
data(xs, ys) |= MARKED;
|
||||
|
||||
SymbolShapeP shape(new SymbolShape);
|
||||
|
||||
// walk around, clockwise
|
||||
xs += 1; // start right of the found point, otherwise last_move might think we came from above
|
||||
int x = xs, y = ys;
|
||||
int old_x = x, old_y = y;
|
||||
int last_move = 1; // 1 = right or down, (as in x|y += 1)
|
||||
do {
|
||||
// the cursor (x,y) is between four pounts:
|
||||
// a b
|
||||
// .
|
||||
// c d
|
||||
bool a = data(x-1, y-1) & FULL;
|
||||
bool b = data(x, y-1) & FULL;
|
||||
bool c = data(x-1, y ) & FULL;
|
||||
bool d = data(x, y ) & FULL;
|
||||
UInt pack = (a << 12) + (b << 8) + (c << 4) + d; // 0xabcd
|
||||
switch (pack) {
|
||||
case 0x0001 : x += 1; break;
|
||||
case 0x0010 : y += 1; break;
|
||||
case 0x0011 : x += 1; break;
|
||||
case 0x0100 : y -= 1; break;
|
||||
case 0x0101 : y -= 1; break;
|
||||
case 0x0110 : y -= last_move; break; // diagonal, we can come here from two sides, from left and right
|
||||
case 0x0111 : y -= 1; break; // last_move indicates which of {b,c} we are 'attached' to
|
||||
case 0x1000 : x -= 1; break;
|
||||
case 0x1001 : x += last_move; break;
|
||||
case 0x1010 : y += 1; break;
|
||||
case 0x1011 : x += 1; break;
|
||||
case 0x1100 : x -= 1; break;
|
||||
case 0x1101 : x -= 1; break;
|
||||
case 0x1110 : y += 1; break;
|
||||
default:
|
||||
throw InternalError(_("in the ground/air"));
|
||||
}
|
||||
|
||||
// add to shape and place a mark
|
||||
shape->points.push_back(intrusive(new ControlPoint(
|
||||
double(x) / data.width,
|
||||
double(y) / data.height
|
||||
)));
|
||||
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the shape
|
||||
last_move = (x + y) - (old_x + old_y);
|
||||
old_x = x;
|
||||
old_y = y;
|
||||
} while (x != xs || y != ys); // we will end up in the start point
|
||||
|
||||
// are we on the inside or the outside?
|
||||
if (data(x-2,y-1) & FULL) {
|
||||
shape->combine = SYMBOL_COMBINE_SUBTRACT;
|
||||
} else {
|
||||
shape->combine = SYMBOL_COMBINE_MERGE;
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
|
||||
SymbolP image_to_symbol(Image& img) {
|
||||
int w = img.GetWidth(), h = img.GetHeight();
|
||||
// 1. threshold the image
|
||||
greyscale(img);
|
||||
threshold(img.GetData(), w, h);
|
||||
// 2. read as many symbol shapes as we can
|
||||
ImageData data = {w,h,img.GetData()};
|
||||
SymbolP symbol(new Symbol);
|
||||
while (true) {
|
||||
SymbolShapeP shape = read_symbol_shape(data);
|
||||
if (!shape) break;
|
||||
symbol->parts.push_back(shape);
|
||||
}
|
||||
reverse(symbol->parts.begin(), symbol->parts.end());
|
||||
return symbol;
|
||||
int w = img.GetWidth(), h = img.GetHeight();
|
||||
// 1. threshold the image
|
||||
greyscale(img);
|
||||
threshold(img.GetData(), w, h);
|
||||
// 2. read as many symbol shapes as we can
|
||||
ImageData data = {w,h,img.GetData()};
|
||||
SymbolP symbol(new Symbol);
|
||||
while (true) {
|
||||
SymbolShapeP shape = read_symbol_shape(data);
|
||||
if (!shape) break;
|
||||
symbol->parts.push_back(shape);
|
||||
}
|
||||
reverse(symbol->parts.begin(), symbol->parts.end());
|
||||
return symbol;
|
||||
}
|
||||
|
||||
SymbolP import_symbol(Image& img) {
|
||||
SymbolP symbol;
|
||||
if (is_mse1_symbol(img)) {
|
||||
Image img2 = img.GetSubImage(wxRect(20,0,40,40));
|
||||
symbol = image_to_symbol(img2);
|
||||
} else if (img.GetWidth() > 100 || img.GetHeight() > 100) {
|
||||
// 100x100 ought to be enough, we trow out most afterwards data anyway
|
||||
Image resampled = img.Rescale(100,100);
|
||||
symbol = image_to_symbol(resampled);
|
||||
} else {
|
||||
symbol = image_to_symbol(img);
|
||||
}
|
||||
simplify_symbol(*symbol);
|
||||
return symbol;
|
||||
SymbolP symbol;
|
||||
if (is_mse1_symbol(img)) {
|
||||
Image img2 = img.GetSubImage(wxRect(20,0,40,40));
|
||||
symbol = image_to_symbol(img2);
|
||||
} else if (img.GetWidth() > 100 || img.GetHeight() > 100) {
|
||||
// 100x100 ought to be enough, we trow out most afterwards data anyway
|
||||
Image resampled = img.Rescale(100,100);
|
||||
symbol = image_to_symbol(resampled);
|
||||
} else {
|
||||
symbol = image_to_symbol(img);
|
||||
}
|
||||
simplify_symbol(*symbol);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,19 +247,19 @@ SymbolP import_symbol(Image& img) {
|
||||
/** A corner is a point that has an angle between tangent greater then a treshold
|
||||
*/
|
||||
void mark_corners(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& current = *shape.getPoint(i);
|
||||
Vector2D before = .6 * shape.getPoint(i-1)->pos + .2 * shape.getPoint(i-2)->pos + .1 * shape.getPoint(i-3)->pos + .1 * shape.getPoint(i-4)->pos;
|
||||
Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos;
|
||||
before = (before - current.pos).normalized();
|
||||
after = (after - current.pos).normalized();
|
||||
if (dot(before,after) >= -0.25f) {
|
||||
// corner
|
||||
current.lock = LOCK_FREE;
|
||||
} else {
|
||||
current.lock = LOCK_DIR;
|
||||
}
|
||||
}
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& current = *shape.getPoint(i);
|
||||
Vector2D before = .6 * shape.getPoint(i-1)->pos + .2 * shape.getPoint(i-2)->pos + .1 * shape.getPoint(i-3)->pos + .1 * shape.getPoint(i-4)->pos;
|
||||
Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos;
|
||||
before = (before - current.pos).normalized();
|
||||
after = (after - current.pos).normalized();
|
||||
if (dot(before,after) >= -0.25f) {
|
||||
// corner
|
||||
current.lock = LOCK_FREE;
|
||||
} else {
|
||||
current.lock = LOCK_DIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge adjacent corners
|
||||
@@ -280,116 +280,116 @@ void mark_corners(SymbolShape& shape) {
|
||||
* is the merged corner. If it is too far away, don't merge
|
||||
*/
|
||||
void merge_corners(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i - 1);
|
||||
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
|
||||
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
|
||||
// and take the one that has the largest angle with ab, i.e. the smallest dot,
|
||||
// where ab is the line between the two corners
|
||||
Vector2D ab = cur.pos - prev.pos;
|
||||
double min_a_dot = 1e100, min_b_dot = 1e100;
|
||||
Vector2D a, b;
|
||||
for (int j = 0 ; j < 4 ; ++j) {
|
||||
Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||
Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized();
|
||||
double a_dot = dot(a_, ab);
|
||||
double b_dot = -dot(b_, ab);
|
||||
if (a_dot < min_a_dot) {
|
||||
min_a_dot = a_dot;
|
||||
a = a_;
|
||||
}
|
||||
if (b_dot < min_b_dot) {
|
||||
min_b_dot = b_dot;
|
||||
b = b_;
|
||||
}
|
||||
}
|
||||
// step 2. find intersection point, to solve:
|
||||
// t a + ab = u b, solve for t,u
|
||||
// Gives us:
|
||||
// t = ab cross b / b cross a
|
||||
double tden = max(0.00000001, cross(b,a));
|
||||
double t = cross(ab,b) / tden;
|
||||
// do these tangent lines intersect, and not too far away?
|
||||
// if so, then the intersection point is the merged point
|
||||
if (t >= 0 && t < 20.0) {
|
||||
prev.pos += a * -t;
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i - 1);
|
||||
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
|
||||
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
|
||||
// and take the one that has the largest angle with ab, i.e. the smallest dot,
|
||||
// where ab is the line between the two corners
|
||||
Vector2D ab = cur.pos - prev.pos;
|
||||
double min_a_dot = 1e100, min_b_dot = 1e100;
|
||||
Vector2D a, b;
|
||||
for (int j = 0 ; j < 4 ; ++j) {
|
||||
Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||
Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized();
|
||||
double a_dot = dot(a_, ab);
|
||||
double b_dot = -dot(b_, ab);
|
||||
if (a_dot < min_a_dot) {
|
||||
min_a_dot = a_dot;
|
||||
a = a_;
|
||||
}
|
||||
if (b_dot < min_b_dot) {
|
||||
min_b_dot = b_dot;
|
||||
b = b_;
|
||||
}
|
||||
}
|
||||
// step 2. find intersection point, to solve:
|
||||
// t a + ab = u b, solve for t,u
|
||||
// Gives us:
|
||||
// t = ab cross b / b cross a
|
||||
double tden = max(0.00000001, cross(b,a));
|
||||
double t = cross(ab,b) / tden;
|
||||
// do these tangent lines intersect, and not too far away?
|
||||
// if so, then the intersection point is the merged point
|
||||
if (t >= 0 && t < 20.0) {
|
||||
prev.pos += a * -t;
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Avarage/'blur' a symbol shape
|
||||
void avarage(SymbolShape& shape) {
|
||||
// create a copy of the points
|
||||
vector<Vector2D> old_points;
|
||||
FOR_EACH(p, shape.points) {
|
||||
old_points.push_back(p->pos);
|
||||
}
|
||||
// avarage points
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& p = *shape.getPoint(i);
|
||||
if (p.lock == LOCK_DIR) {
|
||||
p.pos = .25 * old_points[mod(i-1, old_points.size())]
|
||||
+ .50 * p.pos
|
||||
+ .25 * old_points[mod(i+1, old_points.size())];
|
||||
}
|
||||
}
|
||||
// create a copy of the points
|
||||
vector<Vector2D> old_points;
|
||||
FOR_EACH(p, shape.points) {
|
||||
old_points.push_back(p->pos);
|
||||
}
|
||||
// avarage points
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& p = *shape.getPoint(i);
|
||||
if (p.lock == LOCK_DIR) {
|
||||
p.pos = .25 * old_points[mod(i-1, old_points.size())]
|
||||
+ .50 * p.pos
|
||||
+ .25 * old_points[mod(i+1, old_points.size())];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a symbol shape to curves
|
||||
void convert_to_curves(SymbolShape& shape) {
|
||||
// mark all segments as curves
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
cur.segment_after = SEGMENT_CURVE;
|
||||
cur.segment_before = SEGMENT_CURVE;
|
||||
cur.delta_after = (next.pos - cur.pos) / 3.0;
|
||||
next.delta_before = (cur.pos - next.pos) / 3.0;
|
||||
}
|
||||
// make the curves smooth by enforcing direction constraints
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->onUpdateLock();
|
||||
}
|
||||
// mark all segments as curves
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
cur.segment_after = SEGMENT_CURVE;
|
||||
cur.segment_before = SEGMENT_CURVE;
|
||||
cur.delta_after = (next.pos - cur.pos) / 3.0;
|
||||
next.delta_before = (cur.pos - next.pos) / 3.0;
|
||||
}
|
||||
// make the curves smooth by enforcing direction constraints
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->onUpdateLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert almost straight curves in a symbol shape to lines
|
||||
void straighten(SymbolShape& shape) {
|
||||
const double treshold = 0.2;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
Vector2D ab = (next.pos - cur.pos).normalized();
|
||||
Vector2D aa = cur.delta_after.normalized();
|
||||
Vector2D bb = next.delta_before.normalized();
|
||||
// if the area beneath the polygon formed by the handles is small
|
||||
// then it is a straight line
|
||||
double cpDot = abs(cross(aa,ab)) + abs(cross(bb,ab));
|
||||
if (cpDot < treshold) {
|
||||
cur.segment_after = next.segment_before = SEGMENT_LINE;
|
||||
cur.delta_after = next.delta_before = Vector2D();
|
||||
cur.lock = next.lock = LOCK_FREE;
|
||||
}
|
||||
}
|
||||
const double treshold = 0.2;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
Vector2D ab = (next.pos - cur.pos).normalized();
|
||||
Vector2D aa = cur.delta_after.normalized();
|
||||
Vector2D bb = next.delta_before.normalized();
|
||||
// if the area beneath the polygon formed by the handles is small
|
||||
// then it is a straight line
|
||||
double cpDot = abs(cross(aa,ab)) + abs(cross(bb,ab));
|
||||
if (cpDot < treshold) {
|
||||
cur.segment_after = next.segment_before = SEGMENT_LINE;
|
||||
cur.delta_after = next.delta_before = Vector2D();
|
||||
cur.lock = next.lock = LOCK_FREE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove unneeded points between straight lines
|
||||
void merge_lines(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
if (cur.segment_before != cur.segment_after) continue;
|
||||
Vector2D a = shape.getPoint(i-1)->pos, b = cur.pos, c = shape.getPoint(i+1)->pos;
|
||||
Vector2D ab = (a-b).normalized();
|
||||
Vector2D bc = (b-c).normalized();
|
||||
double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
|
||||
bool keep = angle_len >= .0001;
|
||||
if (!keep) {
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
if (cur.segment_before != cur.segment_after) continue;
|
||||
Vector2D a = shape.getPoint(i-1)->pos, b = cur.pos, c = shape.getPoint(i+1)->pos;
|
||||
Vector2D ab = (a-b).normalized();
|
||||
Vector2D bc = (b-c).normalized();
|
||||
double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
|
||||
bool keep = angle_len >= .0001;
|
||||
if (!keep) {
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double cost_of_point_removal(SymbolShape& shape, int i);
|
||||
@@ -400,83 +400,83 @@ void remove_point(SymbolShape& shape, int i);
|
||||
* stop when the cost becomes too high
|
||||
*/
|
||||
void remove_points(SymbolShape& shape) {
|
||||
const double treshold = 0.0002; // maximum cost
|
||||
while (true) {
|
||||
// Find the point with the lowest cost of removal
|
||||
int best = -1;
|
||||
double best_cost = 1e100;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
double cost = cost_of_point_removal(shape, i);
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
if (best_cost > treshold) break;
|
||||
// ... and remove it
|
||||
remove_point(shape, best);
|
||||
}
|
||||
const double treshold = 0.0002; // maximum cost
|
||||
while (true) {
|
||||
// Find the point with the lowest cost of removal
|
||||
int best = -1;
|
||||
double best_cost = 1e100;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
double cost = cost_of_point_removal(shape, i);
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
if (best_cost > treshold) break;
|
||||
// ... and remove it
|
||||
remove_point(shape, best);
|
||||
}
|
||||
}
|
||||
/// Cost of removing point i from a symbol shape
|
||||
double cost_of_point_removal(SymbolShape& shape, int i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
|
||||
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
Vector2D ac = prev.pos - next.pos;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
Vector2D after0 = prev.delta_after * totl / bl;
|
||||
Vector2D before2 = next.delta_before * totl / al;
|
||||
// determine closest point on the merged curve
|
||||
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
|
||||
double t = bl/totl;
|
||||
Vector2D np = cur.pos - c.pointAt(t);
|
||||
// cost is distance to new point * length of line ~= area added/removed from shape
|
||||
return np.length() * ac.length();
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
|
||||
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
Vector2D ac = prev.pos - next.pos;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
Vector2D after0 = prev.delta_after * totl / bl;
|
||||
Vector2D before2 = next.delta_before * totl / al;
|
||||
// determine closest point on the merged curve
|
||||
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
|
||||
double t = bl/totl;
|
||||
Vector2D np = cur.pos - c.pointAt(t);
|
||||
// cost is distance to new point * length of line ~= area added/removed from shape
|
||||
return np.length() * ac.length();
|
||||
}
|
||||
/// Remove a point from a bezier curve
|
||||
/** See SinglePointRemoveAction for algorithm */
|
||||
void remove_point(SymbolShape& shape, int i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
prev.delta_after *= totl / bl;
|
||||
next.delta_before *= totl / al;
|
||||
// remove
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
prev.delta_after *= totl / bl;
|
||||
next.delta_before *= totl / al;
|
||||
// remove
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
}
|
||||
|
||||
|
||||
void simplify_symbol_shape(SymbolShape& shape) {
|
||||
mark_corners(shape);
|
||||
merge_corners(shape);
|
||||
for (int i = 0 ; i < 3 ; ++i) {
|
||||
avarage(shape);
|
||||
}
|
||||
convert_to_curves(shape);
|
||||
remove_points(shape);
|
||||
straighten(shape);
|
||||
merge_lines(shape);
|
||||
mark_corners(shape);
|
||||
merge_corners(shape);
|
||||
for (int i = 0 ; i < 3 ; ++i) {
|
||||
avarage(shape);
|
||||
}
|
||||
convert_to_curves(shape);
|
||||
remove_points(shape);
|
||||
straighten(shape);
|
||||
merge_lines(shape);
|
||||
}
|
||||
|
||||
void simplify_symbol(Symbol& symbol) {
|
||||
FOR_EACH(pb, symbol.parts) {
|
||||
if (SymbolShape* p = pb->isSymbolShape()) {
|
||||
simplify_symbol_shape(*p);
|
||||
}
|
||||
}
|
||||
FOR_EACH(pb, symbol.parts) {
|
||||
if (SymbolShape* p = pb->isSymbolShape()) {
|
||||
simplify_symbol_shape(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+133
-133
@@ -22,15 +22,15 @@
|
||||
/// The file format of MSE1 files
|
||||
class MSE1FileFormat : public FileFormat {
|
||||
public:
|
||||
virtual String extension() { return _("mse"); }
|
||||
virtual String name() { return _("Magic Set Editor version 1 files (*.mse)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
virtual String extension() { return _("mse"); }
|
||||
virtual String name() { return _("Magic Set Editor version 1 files (*.mse)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
};
|
||||
|
||||
FileFormatP mse1_file_format() {
|
||||
return intrusive(new MSE1FileFormat());
|
||||
return intrusive(new MSE1FileFormat());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Importing
|
||||
@@ -39,134 +39,134 @@ FileFormatP mse1_file_format() {
|
||||
void read_mse1_card(Set& set, wxFileInputStream& f, wxTextInputStream& file);
|
||||
|
||||
SetP MSE1FileFormat::importSet(const String& filename) {
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// file version check
|
||||
String format = file.ReadLine();
|
||||
if (format.substr(0,8) != _("MTG Set8")) {
|
||||
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from http:;//magicsetedtitor.sourceforge.net\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
|
||||
}
|
||||
// read general info
|
||||
set->value<TextValue>(_("title")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("artist")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("copyright")).value = file.ReadLine();
|
||||
file.ReadLine(); // border color, ignored
|
||||
String stylesheet = file.ReadLine();
|
||||
set->apprentice_code = file.ReadLine(); // apprentice prefix
|
||||
file.ReadLine(); // 'formatN'?, not even used by MSE1 :S, ignored
|
||||
file.ReadLine(); // 'formatS'?, same, ignored
|
||||
file.ReadLine(); // symbol filename, ignored
|
||||
file.ReadLine(); // use black symbol for all rarities, ignored
|
||||
String desc, line;
|
||||
while (!f.Eof()) {
|
||||
line = file.ReadLine();
|
||||
if (line == _("\xFF")) break;
|
||||
desc += line;
|
||||
}
|
||||
set->value<TextValue>(_("description")).value = desc;
|
||||
|
||||
// load stylesheet
|
||||
if (stylesheet.substr(0,3) == _("old")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
// read cards
|
||||
CardP current_card;
|
||||
while (!f.Eof()) {
|
||||
read_mse1_card(*set, f, file);
|
||||
}
|
||||
|
||||
// done
|
||||
set->validate();
|
||||
return set;
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// file version check
|
||||
String format = file.ReadLine();
|
||||
if (format.substr(0,8) != _("MTG Set8")) {
|
||||
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from http:;//magicsetedtitor.sourceforge.net\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
|
||||
}
|
||||
// read general info
|
||||
set->value<TextValue>(_("title")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("artist")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("copyright")).value = file.ReadLine();
|
||||
file.ReadLine(); // border color, ignored
|
||||
String stylesheet = file.ReadLine();
|
||||
set->apprentice_code = file.ReadLine(); // apprentice prefix
|
||||
file.ReadLine(); // 'formatN'?, not even used by MSE1 :S, ignored
|
||||
file.ReadLine(); // 'formatS'?, same, ignored
|
||||
file.ReadLine(); // symbol filename, ignored
|
||||
file.ReadLine(); // use black symbol for all rarities, ignored
|
||||
String desc, line;
|
||||
while (!f.Eof()) {
|
||||
line = file.ReadLine();
|
||||
if (line == _("\xFF")) break;
|
||||
desc += line;
|
||||
}
|
||||
set->value<TextValue>(_("description")).value = desc;
|
||||
|
||||
// load stylesheet
|
||||
if (stylesheet.substr(0,3) == _("old")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
// read cards
|
||||
CardP current_card;
|
||||
while (!f.Eof()) {
|
||||
read_mse1_card(*set, f, file);
|
||||
}
|
||||
|
||||
// done
|
||||
set->validate();
|
||||
return set;
|
||||
}
|
||||
|
||||
void read_mse1_card(Set& set, wxFileInputStream& f, wxTextInputStream& file) {
|
||||
CardP card(new Card(*set.game));
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
String line = file.ReadLine();
|
||||
if (line.empty()) continue;
|
||||
Char type = line.GetChar(0);
|
||||
line = line.substr(1);
|
||||
// interpret this line
|
||||
switch (type) {
|
||||
case 'A': { // done
|
||||
set.cards.push_back(card);
|
||||
return;
|
||||
} case 'B': { // name
|
||||
card->value<TextValue>(_("name")) .value.assign(line);
|
||||
break;
|
||||
} case 'C': case 'D': { // image filename
|
||||
String image_file = set.newFileName(_("image"),_("")); // a new unique name in the package
|
||||
if (wxCopyFile(line, set.nameOut(image_file), true)) {
|
||||
card->value<ImageValue>(_("image")) .filename = image_file;
|
||||
}
|
||||
break;
|
||||
} case 'E': { // super type
|
||||
card->value<TextValue>(_("super type")) .value.assign(line);
|
||||
break;
|
||||
} case 'F': { // sub type
|
||||
card->value<TextValue>(_("sub type")) .value.assign(line);
|
||||
break;
|
||||
} case 'G': { // casting cost
|
||||
card->value<TextValue>(_("casting cost")).value.assign(line);
|
||||
break;
|
||||
} case 'H': { // rarity
|
||||
String rarity;
|
||||
if (line == _("(U)")) rarity = _("uncommon");
|
||||
else if (line == _("(R)")) rarity = _("rare");
|
||||
else rarity = _("common");
|
||||
card->value<ChoiceValue>(_("rarity")) .value.assign(rarity);
|
||||
break;
|
||||
} case 'I': { // power/thoughness
|
||||
size_t pos = line.find_first_of(_('/'));
|
||||
if (pos != String::npos) {
|
||||
card->value<TextValue>(_("power")) .value.assign(line.substr(0, pos));
|
||||
card->value<TextValue>(_("toughness")) .value.assign(line.substr(pos+1));
|
||||
}
|
||||
break;
|
||||
} case 'J': { // rule text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("rule text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'K': { // flavor text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("flavor text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'L': { // card color (if not default)
|
||||
// decode color
|
||||
String color;
|
||||
if (line == _("1")) color = _("white");
|
||||
else if (line == _("2")) color = _("blue");
|
||||
else if (line == _("3")) color = _("black");
|
||||
else if (line == _("4")) color = _("red");
|
||||
else if (line == _("5")) color = _("green");
|
||||
else if (line == _("6")) color = _("colorless");
|
||||
else if (line == _("7")) color = _("land");
|
||||
else if (line == _("9")) color = _("multicolor");
|
||||
else color = _("colorless");
|
||||
card->value<ChoiceValue>(_("card color")).value.assign(color);
|
||||
break;
|
||||
} default: {
|
||||
throw ParseError(_("Not a valid MSE1 file"));
|
||||
}
|
||||
}
|
||||
}
|
||||
CardP card(new Card(*set.game));
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
String line = file.ReadLine();
|
||||
if (line.empty()) continue;
|
||||
Char type = line.GetChar(0);
|
||||
line = line.substr(1);
|
||||
// interpret this line
|
||||
switch (type) {
|
||||
case 'A': { // done
|
||||
set.cards.push_back(card);
|
||||
return;
|
||||
} case 'B': { // name
|
||||
card->value<TextValue>(_("name")) .value.assign(line);
|
||||
break;
|
||||
} case 'C': case 'D': { // image filename
|
||||
String image_file = set.newFileName(_("image"),_("")); // a new unique name in the package
|
||||
if (wxCopyFile(line, set.nameOut(image_file), true)) {
|
||||
card->value<ImageValue>(_("image")) .filename = image_file;
|
||||
}
|
||||
break;
|
||||
} case 'E': { // super type
|
||||
card->value<TextValue>(_("super type")) .value.assign(line);
|
||||
break;
|
||||
} case 'F': { // sub type
|
||||
card->value<TextValue>(_("sub type")) .value.assign(line);
|
||||
break;
|
||||
} case 'G': { // casting cost
|
||||
card->value<TextValue>(_("casting cost")).value.assign(line);
|
||||
break;
|
||||
} case 'H': { // rarity
|
||||
String rarity;
|
||||
if (line == _("(U)")) rarity = _("uncommon");
|
||||
else if (line == _("(R)")) rarity = _("rare");
|
||||
else rarity = _("common");
|
||||
card->value<ChoiceValue>(_("rarity")) .value.assign(rarity);
|
||||
break;
|
||||
} case 'I': { // power/thoughness
|
||||
size_t pos = line.find_first_of(_('/'));
|
||||
if (pos != String::npos) {
|
||||
card->value<TextValue>(_("power")) .value.assign(line.substr(0, pos));
|
||||
card->value<TextValue>(_("toughness")) .value.assign(line.substr(pos+1));
|
||||
}
|
||||
break;
|
||||
} case 'J': { // rule text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("rule text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'K': { // flavor text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("flavor text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'L': { // card color (if not default)
|
||||
// decode color
|
||||
String color;
|
||||
if (line == _("1")) color = _("white");
|
||||
else if (line == _("2")) color = _("blue");
|
||||
else if (line == _("3")) color = _("black");
|
||||
else if (line == _("4")) color = _("red");
|
||||
else if (line == _("5")) color = _("green");
|
||||
else if (line == _("6")) color = _("colorless");
|
||||
else if (line == _("7")) color = _("land");
|
||||
else if (line == _("9")) color = _("multicolor");
|
||||
else color = _("colorless");
|
||||
card->value<ChoiceValue>(_("card color")).value.assign(color);
|
||||
break;
|
||||
} default: {
|
||||
throw ParseError(_("Not a valid MSE1 file"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-27
@@ -16,34 +16,34 @@
|
||||
/// The file format of MSE2 files
|
||||
class MSE2FileFormat : public FileFormat {
|
||||
public:
|
||||
virtual String extension() { return _("mse-set"); }
|
||||
virtual String matches() { return _("*.mse-set;set"); }
|
||||
virtual String name() { return _("Magic Set Editor sets (*.mse-set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return true; }
|
||||
virtual SetP importSet(const String& filename) {
|
||||
wxString set_name = filename;
|
||||
// Strip "/set" from the end, newer wx versions have a function for this:
|
||||
// filename.EndsWith(_("/set"), &set_name);
|
||||
if (filename.size() > 4 && filename.substr(filename.size()-4) == _("/set")) {
|
||||
set_name = filename.substr(0, filename.size()-4);
|
||||
}
|
||||
SetP set(new Set);
|
||||
set->open(set_name);
|
||||
settings.addRecentFile(set_name);
|
||||
return set;
|
||||
}
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy) {
|
||||
if (is_copy) {
|
||||
set.saveCopy(filename);
|
||||
} else {
|
||||
set.saveAs(filename);
|
||||
settings.addRecentFile(filename);
|
||||
set.actions.setSavePoint();
|
||||
}
|
||||
}
|
||||
virtual String extension() { return _("mse-set"); }
|
||||
virtual String matches() { return _("*.mse-set;set"); }
|
||||
virtual String name() { return _("Magic Set Editor sets (*.mse-set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return true; }
|
||||
virtual SetP importSet(const String& filename) {
|
||||
wxString set_name = filename;
|
||||
// Strip "/set" from the end, newer wx versions have a function for this:
|
||||
// filename.EndsWith(_("/set"), &set_name);
|
||||
if (filename.size() > 4 && filename.substr(filename.size()-4) == _("/set")) {
|
||||
set_name = filename.substr(0, filename.size()-4);
|
||||
}
|
||||
SetP set(new Set);
|
||||
set->open(set_name);
|
||||
settings.addRecentFile(set_name);
|
||||
return set;
|
||||
}
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy) {
|
||||
if (is_copy) {
|
||||
set.saveCopy(filename);
|
||||
} else {
|
||||
set.saveAs(filename);
|
||||
settings.addRecentFile(filename);
|
||||
set.actions.setSavePoint();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FileFormatP mse2_file_format() {
|
||||
return intrusive(new MSE2FileFormat());
|
||||
return intrusive(new MSE2FileFormat());
|
||||
}
|
||||
|
||||
+225
-225
@@ -25,253 +25,253 @@ DECLARE_TYPEOF_COLLECTION(CardP);
|
||||
/// The file format of Mtg Editor files
|
||||
class MtgEditorFileFormat : public FileFormat {
|
||||
public:
|
||||
virtual String extension() { return _("set"); }
|
||||
virtual String name() { return _("Mtg Editor files (*.set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
virtual String extension() { return _("set"); }
|
||||
virtual String name() { return _("Mtg Editor files (*.set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
private:
|
||||
// Filter: se filename -> image directory
|
||||
// based on MtgEditor's: CardSet.getImageFolder
|
||||
String filter1(const String& str);
|
||||
// Filter: card name -> image name
|
||||
// based on MtgEditor's: Tools::purgeSpecialChars
|
||||
String filter2(const String& str);
|
||||
// Remove mtg editor tags
|
||||
void untag(const CardP& card, const String& field);
|
||||
// Translate all tags, mana tags get converted to <sym>, other tags are removed
|
||||
void translateTags(String& value);
|
||||
// Filter: se filename -> image directory
|
||||
// based on MtgEditor's: CardSet.getImageFolder
|
||||
String filter1(const String& str);
|
||||
// Filter: card name -> image name
|
||||
// based on MtgEditor's: Tools::purgeSpecialChars
|
||||
String filter2(const String& str);
|
||||
// Remove mtg editor tags
|
||||
void untag(const CardP& card, const String& field);
|
||||
// Translate all tags, mana tags get converted to <sym>, other tags are removed
|
||||
void translateTags(String& value);
|
||||
};
|
||||
|
||||
FileFormatP mtg_editor_file_format() {
|
||||
return intrusive(new MtgEditorFileFormat());
|
||||
return intrusive(new MtgEditorFileFormat());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Importing
|
||||
|
||||
SetP MtgEditorFileFormat::importSet(const String& filename) {
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// parsing state
|
||||
CardP current_card;
|
||||
Defaultable<String>* target = nullptr; // value we are currently reading
|
||||
String layout = _("8e");
|
||||
String set_date, card_date;
|
||||
bool first = true;
|
||||
|
||||
// read file
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
if (!current_card) current_card = intrusive(new Card(*set->game));
|
||||
String line = file.ReadLine();
|
||||
if (line == _("#SET###########")) { // set.title
|
||||
target = &set->value<TextValue>(_("title")).value;
|
||||
} else if (line == _("#SETDATE#######")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
set_date = file.ReadLine();
|
||||
} else if (line == _("#SHOWRARITY####") ||
|
||||
line == _("#FONTS#########") || line == _("#COUNT#########")) { // ignore
|
||||
target = nullptr;
|
||||
file.ReadLine();
|
||||
} else if (line == _("#LAYOUT########")) { // layout type
|
||||
target = nullptr;
|
||||
layout = file.ReadLine();
|
||||
} else if (line == _("#CARD##########") || line == _("#EOF###########")) { // begin/end of card
|
||||
if (!first) {
|
||||
// First [CARD##] indicates only the start of a card, subsequent ones also the end of the previous
|
||||
// card. We only care about the latter
|
||||
// remove all tags from all text values
|
||||
untag(current_card, _("name"));
|
||||
untag(current_card, _("super type"));
|
||||
untag(current_card, _("sub type"));
|
||||
untag(current_card, _("casting cost"));
|
||||
untag(current_card, _("flavor text"));
|
||||
untag(current_card, _("illustrator"));
|
||||
untag(current_card, _("copyright"));
|
||||
untag(current_card, _("power"));
|
||||
untag(current_card, _("toughness"));
|
||||
// translate mtg editor tags to mse2 tags
|
||||
translateTags(current_card->value<TextValue>(_("rule text")).value.mutate());
|
||||
// add the card to the set
|
||||
set->cards.push_back(current_card);
|
||||
}
|
||||
first = false;
|
||||
current_card = intrusive(new Card(*set->game));
|
||||
target = ¤t_card->value<TextValue>(_("name")).value;
|
||||
} else if (line == _("#DATE##########")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
card_date = file.ReadLine();
|
||||
} else if (line == _("#TYPE##########")) { // super type
|
||||
target = ¤t_card->value<TextValue>(_("super type")).value;
|
||||
} else if (line == _("#SUBTYPE#######")) { // sub type
|
||||
target = ¤t_card->value<TextValue>(_("sub type")).value;
|
||||
} else if (line == _("#COST##########")) { // casting cost
|
||||
target = ¤t_card->value<TextValue>(_("casting cost")).value;
|
||||
} else if (line == _("#RARITY########") || line == _("#FREQUENCY#####")) { // rarity
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("0")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("common"));
|
||||
else if (line == _("1")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("uncommon"));
|
||||
else current_card->value<ChoiceValue>(_("rarity")).value.assign(_("rare"));
|
||||
} else if (line == _("#COLOR#########")) { // card color
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card color")).value.assign(line);
|
||||
} else if (line == _("#AUTOBG########")) { // card color.isDefault
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("TRUE")) {
|
||||
current_card->value<ChoiceValue>(_("card color")).value.makeDefault();
|
||||
}
|
||||
} else if (line == _("#RULETEXT######")) { // rule text
|
||||
target = ¤t_card->value<TextValue>(_("rule text")).value;
|
||||
} else if (line == _("#FLAVORTEXT####")) { // flavor text
|
||||
target = ¤t_card->value<TextValue>(_("flavor text")).value;
|
||||
} else if (line == _("#ARTIST########")) { // illustrator
|
||||
target = ¤t_card->value<TextValue>(_("illustrator")).value;
|
||||
} else if (line == _("#COPYRIGHT#####")) { // copyright
|
||||
target = ¤t_card->value<TextValue>(_("copyright")).value;
|
||||
} else if (line == _("#POWER#########")) { // power
|
||||
target = ¤t_card->value<TextValue>(_("power")).value;
|
||||
} else if (line == _("#TOUGHNESS#####")) { // toughness
|
||||
target = ¤t_card->value<TextValue>(_("toughness")).value;
|
||||
} else if (line == _("#ILLUSTRATION##") || line == _("#ILLUSTRATION8#")) { // image
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (!wxFileExists(line)) {
|
||||
// based on card name and date
|
||||
line = filter1(filename) + set_date + _("/") +
|
||||
filter2(current_card->value<TextValue>(_("name")).value) + card_date + _(".jpg");
|
||||
}
|
||||
// copy image into set
|
||||
if (wxFileExists(line)) {
|
||||
String image_file = set->newFileName(_("image"),_(""));
|
||||
if (wxCopyFile(line, set->nameOut(image_file), true)) {
|
||||
current_card->value<ImageValue>(_("image")).filename = image_file;
|
||||
}
|
||||
}
|
||||
} else if (line == _("#TOMBSTONE#####")) { // tombstone
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card symbol")).value.assign(
|
||||
line==_("TRUE") ? _("tombstone") : _("none")
|
||||
);
|
||||
} else {
|
||||
// normal text
|
||||
if (target != 0) { // value of a text field
|
||||
if (!target->isDefault()) target->mutate() += _("\n");
|
||||
target->mutate() += line;
|
||||
} else {
|
||||
throw ParseError(_("Error in Mtg Editor file, unexpected text:\n") + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set defaults for artist and copyright to that of the first card
|
||||
if (!set->cards.empty()) {
|
||||
String artist = set->cards[0]->value<TextValue>(_("illustrator")).value;
|
||||
String copyright = set->cards[0]->value<TextValue>(_("copyright")) .value;
|
||||
set->value<TextValue>(_("artist")) .value.assign(artist);
|
||||
set->value<TextValue>(_("copyright")).value.assign(copyright);
|
||||
// which cards have this value?
|
||||
FOR_EACH(card, set->cards) {
|
||||
Defaultable<String>& card_artist = card->value<TextValue>(_("illustrator")).value;
|
||||
Defaultable<String>& card_copyright = card->value<TextValue>(_("copyright")) .value;
|
||||
if (card_artist == artist) card_artist.makeDefault();
|
||||
if (card_copyright == copyright) card_copyright.makeDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Load stylesheet
|
||||
if (layout != _("8e")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
set->validate();
|
||||
return set;
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// parsing state
|
||||
CardP current_card;
|
||||
Defaultable<String>* target = nullptr; // value we are currently reading
|
||||
String layout = _("8e");
|
||||
String set_date, card_date;
|
||||
bool first = true;
|
||||
|
||||
// read file
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
if (!current_card) current_card = intrusive(new Card(*set->game));
|
||||
String line = file.ReadLine();
|
||||
if (line == _("#SET###########")) { // set.title
|
||||
target = &set->value<TextValue>(_("title")).value;
|
||||
} else if (line == _("#SETDATE#######")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
set_date = file.ReadLine();
|
||||
} else if (line == _("#SHOWRARITY####") ||
|
||||
line == _("#FONTS#########") || line == _("#COUNT#########")) { // ignore
|
||||
target = nullptr;
|
||||
file.ReadLine();
|
||||
} else if (line == _("#LAYOUT########")) { // layout type
|
||||
target = nullptr;
|
||||
layout = file.ReadLine();
|
||||
} else if (line == _("#CARD##########") || line == _("#EOF###########")) { // begin/end of card
|
||||
if (!first) {
|
||||
// First [CARD##] indicates only the start of a card, subsequent ones also the end of the previous
|
||||
// card. We only care about the latter
|
||||
// remove all tags from all text values
|
||||
untag(current_card, _("name"));
|
||||
untag(current_card, _("super type"));
|
||||
untag(current_card, _("sub type"));
|
||||
untag(current_card, _("casting cost"));
|
||||
untag(current_card, _("flavor text"));
|
||||
untag(current_card, _("illustrator"));
|
||||
untag(current_card, _("copyright"));
|
||||
untag(current_card, _("power"));
|
||||
untag(current_card, _("toughness"));
|
||||
// translate mtg editor tags to mse2 tags
|
||||
translateTags(current_card->value<TextValue>(_("rule text")).value.mutate());
|
||||
// add the card to the set
|
||||
set->cards.push_back(current_card);
|
||||
}
|
||||
first = false;
|
||||
current_card = intrusive(new Card(*set->game));
|
||||
target = ¤t_card->value<TextValue>(_("name")).value;
|
||||
} else if (line == _("#DATE##########")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
card_date = file.ReadLine();
|
||||
} else if (line == _("#TYPE##########")) { // super type
|
||||
target = ¤t_card->value<TextValue>(_("super type")).value;
|
||||
} else if (line == _("#SUBTYPE#######")) { // sub type
|
||||
target = ¤t_card->value<TextValue>(_("sub type")).value;
|
||||
} else if (line == _("#COST##########")) { // casting cost
|
||||
target = ¤t_card->value<TextValue>(_("casting cost")).value;
|
||||
} else if (line == _("#RARITY########") || line == _("#FREQUENCY#####")) { // rarity
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("0")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("common"));
|
||||
else if (line == _("1")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("uncommon"));
|
||||
else current_card->value<ChoiceValue>(_("rarity")).value.assign(_("rare"));
|
||||
} else if (line == _("#COLOR#########")) { // card color
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card color")).value.assign(line);
|
||||
} else if (line == _("#AUTOBG########")) { // card color.isDefault
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("TRUE")) {
|
||||
current_card->value<ChoiceValue>(_("card color")).value.makeDefault();
|
||||
}
|
||||
} else if (line == _("#RULETEXT######")) { // rule text
|
||||
target = ¤t_card->value<TextValue>(_("rule text")).value;
|
||||
} else if (line == _("#FLAVORTEXT####")) { // flavor text
|
||||
target = ¤t_card->value<TextValue>(_("flavor text")).value;
|
||||
} else if (line == _("#ARTIST########")) { // illustrator
|
||||
target = ¤t_card->value<TextValue>(_("illustrator")).value;
|
||||
} else if (line == _("#COPYRIGHT#####")) { // copyright
|
||||
target = ¤t_card->value<TextValue>(_("copyright")).value;
|
||||
} else if (line == _("#POWER#########")) { // power
|
||||
target = ¤t_card->value<TextValue>(_("power")).value;
|
||||
} else if (line == _("#TOUGHNESS#####")) { // toughness
|
||||
target = ¤t_card->value<TextValue>(_("toughness")).value;
|
||||
} else if (line == _("#ILLUSTRATION##") || line == _("#ILLUSTRATION8#")) { // image
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (!wxFileExists(line)) {
|
||||
// based on card name and date
|
||||
line = filter1(filename) + set_date + _("/") +
|
||||
filter2(current_card->value<TextValue>(_("name")).value) + card_date + _(".jpg");
|
||||
}
|
||||
// copy image into set
|
||||
if (wxFileExists(line)) {
|
||||
String image_file = set->newFileName(_("image"),_(""));
|
||||
if (wxCopyFile(line, set->nameOut(image_file), true)) {
|
||||
current_card->value<ImageValue>(_("image")).filename = image_file;
|
||||
}
|
||||
}
|
||||
} else if (line == _("#TOMBSTONE#####")) { // tombstone
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card symbol")).value.assign(
|
||||
line==_("TRUE") ? _("tombstone") : _("none")
|
||||
);
|
||||
} else {
|
||||
// normal text
|
||||
if (target != 0) { // value of a text field
|
||||
if (!target->isDefault()) target->mutate() += _("\n");
|
||||
target->mutate() += line;
|
||||
} else {
|
||||
throw ParseError(_("Error in Mtg Editor file, unexpected text:\n") + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set defaults for artist and copyright to that of the first card
|
||||
if (!set->cards.empty()) {
|
||||
String artist = set->cards[0]->value<TextValue>(_("illustrator")).value;
|
||||
String copyright = set->cards[0]->value<TextValue>(_("copyright")) .value;
|
||||
set->value<TextValue>(_("artist")) .value.assign(artist);
|
||||
set->value<TextValue>(_("copyright")).value.assign(copyright);
|
||||
// which cards have this value?
|
||||
FOR_EACH(card, set->cards) {
|
||||
Defaultable<String>& card_artist = card->value<TextValue>(_("illustrator")).value;
|
||||
Defaultable<String>& card_copyright = card->value<TextValue>(_("copyright")) .value;
|
||||
if (card_artist == artist) card_artist.makeDefault();
|
||||
if (card_copyright == copyright) card_copyright.makeDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Load stylesheet
|
||||
if (layout != _("8e")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
set->validate();
|
||||
return set;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Filtering
|
||||
|
||||
String MtgEditorFileFormat::filter1(const String& str) {
|
||||
String before, after, ret;
|
||||
// split path name
|
||||
size_t pos = str.find_last_of(_("/\\"));
|
||||
if (pos == String::npos) {
|
||||
before = _(""); after = str;
|
||||
} else {
|
||||
before = str.substr(0, pos + 1);
|
||||
after = str.substr(pos + 1);
|
||||
}
|
||||
// filter
|
||||
FOR_EACH(c, after) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else ret += _('_');
|
||||
}
|
||||
return before + ret;
|
||||
String before, after, ret;
|
||||
// split path name
|
||||
size_t pos = str.find_last_of(_("/\\"));
|
||||
if (pos == String::npos) {
|
||||
before = _(""); after = str;
|
||||
} else {
|
||||
before = str.substr(0, pos + 1);
|
||||
after = str.substr(pos + 1);
|
||||
}
|
||||
// filter
|
||||
FOR_EACH(c, after) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else ret += _('_');
|
||||
}
|
||||
return before + ret;
|
||||
}
|
||||
|
||||
String MtgEditorFileFormat::filter2(const String& str) {
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else if (c==_(' ') || c==_('-')) ret += _('_');
|
||||
}
|
||||
return ret;
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else if (c==_(' ') || c==_('-')) ret += _('_');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MtgEditorFileFormat::untag(const CardP& card, const String& field) {
|
||||
Defaultable<String>& value = card->value<TextValue>(field).value;
|
||||
value.assignDontChangeDefault(untag_no_escape(value));
|
||||
Defaultable<String>& value = card->value<TextValue>(field).value;
|
||||
value.assignDontChangeDefault(untag_no_escape(value));
|
||||
}
|
||||
|
||||
|
||||
void MtgEditorFileFormat::translateTags(String& value) {
|
||||
// Translate tags
|
||||
String ret;
|
||||
size_t pos = 0;
|
||||
while (pos < value.size()) {
|
||||
Char c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('<')) {
|
||||
String tag;
|
||||
while (pos < value.size()) {
|
||||
c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('>')) break;
|
||||
else tag += c;
|
||||
}
|
||||
tag.MakeUpper();
|
||||
unsigned long number;
|
||||
if (tag==_("W") || tag==_("U") || tag==_("B") || tag==_("R") || tag==_("G") || tag==_("X") || tag==_("Y") || tag==_("Z")) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag.ToULong(&number)) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag==_("T") || tag==_("TAP")) {
|
||||
ret += _("<sym>T</sym>");
|
||||
} else if (tag==_("THIS")) {
|
||||
ret += _("~");
|
||||
}
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
// Join adjecent symbol sections
|
||||
value = replace_all(ret, _("</sym><sym>"), _(""));
|
||||
// Translate tags
|
||||
String ret;
|
||||
size_t pos = 0;
|
||||
while (pos < value.size()) {
|
||||
Char c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('<')) {
|
||||
String tag;
|
||||
while (pos < value.size()) {
|
||||
c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('>')) break;
|
||||
else tag += c;
|
||||
}
|
||||
tag.MakeUpper();
|
||||
unsigned long number;
|
||||
if (tag==_("W") || tag==_("U") || tag==_("B") || tag==_("R") || tag==_("G") || tag==_("X") || tag==_("Y") || tag==_("Z")) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag.ToULong(&number)) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag==_("T") || tag==_("TAP")) {
|
||||
ret += _("<sym>T</sym>");
|
||||
} else if (tag==_("THIS")) {
|
||||
ret += _("~");
|
||||
}
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
// Join adjecent symbol sections
|
||||
value = replace_all(ret, _("</sym><sym>"), _(""));
|
||||
}
|
||||
|
||||
+70
-70
@@ -23,89 +23,89 @@ DECLARE_TYPEOF_COLLECTION(CardP);
|
||||
|
||||
/// Convert a tagged string to MWS format: \t\t before each line beyond the first
|
||||
String untag_mws(const String& str) {
|
||||
// TODO : em dashes?
|
||||
return replace_all(untag(curly_quotes(str,false)), _("\n"), _("\n\t\t") );
|
||||
// TODO : em dashes?
|
||||
return replace_all(untag(curly_quotes(str,false)), _("\n"), _("\n\t\t") );
|
||||
}
|
||||
//String untag_mws(const Defaultable<String>& str) {
|
||||
// str.
|
||||
// str.
|
||||
//}
|
||||
|
||||
/// Code for card color in MWS format
|
||||
String card_color_mws(const String& col) {
|
||||
if (col == _("white")) return _("W");
|
||||
if (col == _("blue")) return _("U");
|
||||
if (col == _("black")) return _("B");
|
||||
if (col == _("red")) return _("R");
|
||||
if (col == _("green")) return _("G");
|
||||
if (col == _("artifact")) return _("Art");
|
||||
if (col == _("colorless")) return _("Art");
|
||||
if (col.find(_("land")) != String::npos) {
|
||||
return _("Lnd"); // land
|
||||
} else {
|
||||
return _("Gld"); // multicolor
|
||||
}
|
||||
if (col == _("white")) return _("W");
|
||||
if (col == _("blue")) return _("U");
|
||||
if (col == _("black")) return _("B");
|
||||
if (col == _("red")) return _("R");
|
||||
if (col == _("green")) return _("G");
|
||||
if (col == _("artifact")) return _("Art");
|
||||
if (col == _("colorless")) return _("Art");
|
||||
if (col.find(_("land")) != String::npos) {
|
||||
return _("Lnd"); // land
|
||||
} else {
|
||||
return _("Gld"); // multicolor
|
||||
}
|
||||
}
|
||||
|
||||
/// Code for card rarity, used for MWS and Apprentice
|
||||
String card_rarity_code(const String& rarity) {
|
||||
if (rarity == _("rare")) return _("R");
|
||||
if (rarity == _("uncommon")) return _("U");
|
||||
else return _("C");
|
||||
if (rarity == _("rare")) return _("R");
|
||||
if (rarity == _("uncommon")) return _("U");
|
||||
else return _("C");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : export_mws
|
||||
|
||||
void export_mws(Window* parent, const SetP& set) {
|
||||
if (!set->game->isMagic()) {
|
||||
throw Error(_("Can only export Magic sets to Magic Workstation"));
|
||||
}
|
||||
|
||||
// Select filename
|
||||
String name = wxFileSelector(_("Export to file"),settings.default_export_dir,_(""),_(""),
|
||||
_("Text files (*.txt)|*.txt|All Files|*"),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT, parent);
|
||||
if (name.empty()) return;
|
||||
settings.default_export_dir = wxPathOnly(name);
|
||||
wxBusyCursor busy;
|
||||
// Open file
|
||||
wxFileOutputStream f(name);
|
||||
wxTextOutputStream file(f, wxEOL_DOS);
|
||||
|
||||
// Write header
|
||||
file.WriteString(set->value<TextValue>(_("title")).value + _(" Spoiler List\n"));
|
||||
file.WriteString(_("Set exported using Magic Set Editor 2, version ") + app_version.toString() + _("\n\n"));
|
||||
wxDateTime now = wxDateTime::Now();
|
||||
file.WriteString(_("Spoiler List created on ") + now.FormatISODate() + _(" ") + now.FormatISOTime());
|
||||
file.WriteString(_("\n\n"));
|
||||
|
||||
// Write cards
|
||||
FOR_EACH(card, set->cards) {
|
||||
file.WriteString(_("Card Name:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("name")).value));
|
||||
file.WriteString(_("\nCard Color:\t"));
|
||||
file.WriteString(card_color_mws(card->value<ChoiceValue>(_("card color")).value));
|
||||
file.WriteString(_("\nMana Cost:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("casting cost")).value));
|
||||
file.WriteString(_("\nType & Class:\t"));
|
||||
String sup_type = untag_mws(card->value<TextValue>(_("super type")).value);
|
||||
String sub_type = untag_mws(card->value<TextValue>(_("sub type")).value);
|
||||
if (sub_type.empty()) {
|
||||
file.WriteString(sup_type);
|
||||
} else {
|
||||
file.WriteString(sup_type + _(" - ") + sub_type);
|
||||
}
|
||||
file.WriteString(_("\nPow/Tou:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("pt")).value));
|
||||
file.WriteString(_("\nCard Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("rule text")).value));
|
||||
file.WriteString(_("\nFlavor Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("flavor text")).value));
|
||||
file.WriteString(_("\nArtist:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("illustrator")).value));
|
||||
file.WriteString(_("\nRarity:\t\t"));
|
||||
file.WriteString(card_rarity_code(card->value<ChoiceValue>(_("rarity")).value));
|
||||
file.WriteString(_("\nCard #:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("card number")).value));
|
||||
file.WriteString(_("\n\n"));
|
||||
}
|
||||
if (!set->game->isMagic()) {
|
||||
throw Error(_("Can only export Magic sets to Magic Workstation"));
|
||||
}
|
||||
|
||||
// Select filename
|
||||
String name = wxFileSelector(_("Export to file"),settings.default_export_dir,_(""),_(""),
|
||||
_("Text files (*.txt)|*.txt|All Files|*"),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT, parent);
|
||||
if (name.empty()) return;
|
||||
settings.default_export_dir = wxPathOnly(name);
|
||||
wxBusyCursor busy;
|
||||
// Open file
|
||||
wxFileOutputStream f(name);
|
||||
wxTextOutputStream file(f, wxEOL_DOS);
|
||||
|
||||
// Write header
|
||||
file.WriteString(set->value<TextValue>(_("title")).value + _(" Spoiler List\n"));
|
||||
file.WriteString(_("Set exported using Magic Set Editor 2, version ") + app_version.toString() + _("\n\n"));
|
||||
wxDateTime now = wxDateTime::Now();
|
||||
file.WriteString(_("Spoiler List created on ") + now.FormatISODate() + _(" ") + now.FormatISOTime());
|
||||
file.WriteString(_("\n\n"));
|
||||
|
||||
// Write cards
|
||||
FOR_EACH(card, set->cards) {
|
||||
file.WriteString(_("Card Name:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("name")).value));
|
||||
file.WriteString(_("\nCard Color:\t"));
|
||||
file.WriteString(card_color_mws(card->value<ChoiceValue>(_("card color")).value));
|
||||
file.WriteString(_("\nMana Cost:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("casting cost")).value));
|
||||
file.WriteString(_("\nType & Class:\t"));
|
||||
String sup_type = untag_mws(card->value<TextValue>(_("super type")).value);
|
||||
String sub_type = untag_mws(card->value<TextValue>(_("sub type")).value);
|
||||
if (sub_type.empty()) {
|
||||
file.WriteString(sup_type);
|
||||
} else {
|
||||
file.WriteString(sup_type + _(" - ") + sub_type);
|
||||
}
|
||||
file.WriteString(_("\nPow/Tou:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("pt")).value));
|
||||
file.WriteString(_("\nCard Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("rule text")).value));
|
||||
file.WriteString(_("\nFlavor Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("flavor text")).value));
|
||||
file.WriteString(_("\nArtist:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("illustrator")).value));
|
||||
file.WriteString(_("\nRarity:\t\t"));
|
||||
file.WriteString(card_rarity_code(card->value<ChoiceValue>(_("rarity")).value));
|
||||
file.WriteString(_("\nCard #:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("card number")).value));
|
||||
file.WriteString(_("\n\n"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user