diff --git a/data/magic.mse-game/magic-words.en_US.dic b/data/magic.mse-game/magic-words.en_US.dic new file mode 100644 index 00000000..cfb447e6 --- /dev/null +++ b/data/magic.mse-game/magic-words.en_US.dic @@ -0,0 +1,34 @@ +33 +mana +untap/MSDRJZG +face-down +nonwhite +nonblue +nonblack +nonred +nongreen +non-land +unblock/USDG +precombat +postcombat +scry +Plainswalk +Islandwalk +Swampwalk +Mountainwalk +Forestwalk +Landwalk +Desertwalk +Plainshome +Islandhome +Swamphome +Mountainhome +Foresthome +Landhome +Soulshift +Ninjitsu +Bushido +Lifelink +Gravestorm +Fateseal +Bloodthirst diff --git a/data/magic.mse-game/script b/data/magic.mse-game/script index 45e08daa..3bcc099c 100644 --- a/data/magic.mse-game/script +++ b/data/magic.mse-game/script @@ -334,8 +334,17 @@ mana_context := | [ ]* # keyword argument that is declared as cost | , # keyword argument that is declared as cost "; + # truncates the name of legends legend_filter := replace@(match:"(, | of | the ).*", replace: "" ) + +# these are considered a correct 'word' for spellchecking in the text box: +additional_text_words := match@(match: + "(?ix)^(?: # match whole word + ]*>.*?]*> # cardnames and stuff + | [+-]?[0-9X]+ / [+-]?[0-9X]+ # '3/3', '+X/+X' + )$") + # the rule text filter # - adds mana symbols # - makes text in parentheses italic @@ -413,7 +422,11 @@ text_filter := replace: { _1 + to_upper(_2) }) + curly_quotes + # step 9 : spellcheck - { check_spelling(language:language().spellcheck_code) } + { check_spelling( + language: language().spellcheck_code, + extra_dictionary: "/magic.mse-game/magic-words", + extra_match: additional_text_words + )} ############################################################## Other boxes diff --git a/src/gui/symbol/control.cpp b/src/gui/symbol/control.cpp index 808ad857..c8bb28b9 100644 --- a/src/gui/symbol/control.cpp +++ b/src/gui/symbol/control.cpp @@ -193,6 +193,12 @@ void SymbolControl::draw(DC& dc) { } dc.SetLogicalFunction(wxCOPY); } + // draw aspect ratio indicators + double ar = symbol->aspectRatio(); + // TODO: limit aspect ratio + if (ar > 0) { + } else if (ar < 0) { + } // draw editing overlay if (editor) { editor->draw(dc); diff --git a/src/mse.vcproj b/src/mse.vcproj index 899ba430..46d5bcd7 100644 --- a/src/mse.vcproj +++ b/src/mse.vcproj @@ -3948,11 +3948,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/script/functions/spelling.cpp b/src/script/functions/spelling.cpp index 31c840f9..e57515f9 100644 --- a/src/script/functions/spelling.cpp +++ b/src/script/functions/spelling.cpp @@ -15,7 +15,7 @@ // ----------------------------------------------------------------------------- : Functions -bool spelled_correctly(const String& input, size_t start, size_t end, SpellChecker& checker) { +inline bool spelled_correctly(const String& input, size_t start, size_t end, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) { // untag String word = untag(input.substr(start,end-start)); if (word.empty()) return true; @@ -33,28 +33,48 @@ bool spelled_correctly(const String& input, size_t start, size_t end, SpellCheck // symbols are always spelled correctly return true; } - // run through spellchecker - return checker.spell(word.substr(start_u,end_u)); + // run through spellchecker(s) + word.erase(end_u,String::npos); + word.erase(0,start_u); + for (SpellChecker** c = checkers ; *c ; ++c) { + if ((*c)->spell(word)) { + return true; + } + } + // run through additional words regex + if (extra_test) { + ctx.setVariable(SCRIPT_VAR_input, to_script(input.substr(start2,end2-start2))); + if (*extra_test->eval(ctx)) { + return true; + } + } + return false; } -void check_word(const String& input, String& out, size_t start, size_t end, SpellChecker& checker) { +void check_word(const String& input, String& out, size_t start, size_t end, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) { if (start >= end) return; - bool good = spelled_correctly(input, start, end, checker); + bool good = spelled_correctly(input, start, end, checkers, extra_test, ctx); if (!good) out += _(""); out.append(input, start, end-start); if (!good) out += _(""); } SCRIPT_FUNCTION(check_spelling) { - SCRIPT_PARAM(String,language); - SCRIPT_PARAM(String,input); - if (language.empty()) { - // no language -> spelling checking - SCRIPT_RETURN(true); - } - SpellChecker& checker = SpellChecker::get(language); + SCRIPT_PARAM_C(String,language); + SCRIPT_PARAM_C(String,input); + SCRIPT_OPTIONAL_PARAM_N_(String,_("extra dictionary"),extra_dictionary); + SCRIPT_OPTIONAL_PARAM_N_(ScriptValueP,_("extra match"),extra_match); // remove old spelling error tags input = remove_tag(input, _(" spelling checking + if (language.empty()) { + SCRIPT_RETURN(input); + } + SpellChecker* checkers[3] = {nullptr}; + checkers[0] = &SpellChecker::get(language); + if (!extra_dictionary.empty()) { + checkers[1] = &SpellChecker::get(extra_dictionary,language); + } // now walk over the words in the input, and mark misspellings String result; size_t word_start = 0, word_end = 0, pos = 0; @@ -71,7 +91,7 @@ SCRIPT_FUNCTION(check_spelling) { } } else if (isSpace(c) || c == EM_DASH || c == EN_DASH) { // word boundary -> check word - check_word(input, result, word_start, word_end, checker); + check_word(input, result, word_start, word_end, checkers, extra_match, ctx); // non-word characters result.append(input, word_end, pos - word_end + 1); // next @@ -81,15 +101,15 @@ SCRIPT_FUNCTION(check_spelling) { } } // last word - check_word(input, result, word_start, word_end, checker); + check_word(input, result, word_start, word_end, checkers, extra_match, ctx); result.append(input, word_end, String::npos); // done SCRIPT_RETURN(result); } SCRIPT_FUNCTION(check_spelling_word) { - SCRIPT_PARAM(String,language); - SCRIPT_PARAM(String,input); + SCRIPT_PARAM_C(String,language); + SCRIPT_PARAM_C(String,input); if (language.empty()) { // no language -> spelling checking SCRIPT_RETURN(true); diff --git a/src/script/script.cpp b/src/script/script.cpp index 9282b294..4ce3bd27 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -77,6 +77,7 @@ void init_script_variables() { Var(styling); Var(value); Var(condition); + Var(language); assert(variables.size() == SCRIPT_VAR_CUSTOM_FIRST); } diff --git a/src/script/script.hpp b/src/script/script.hpp index f1f667bc..74dec9c7 100644 --- a/src/script/script.hpp +++ b/src/script/script.hpp @@ -136,6 +136,7 @@ enum Variable , SCRIPT_VAR_styling , SCRIPT_VAR_value , SCRIPT_VAR_condition +, SCRIPT_VAR_language , SCRIPT_VAR_CUSTOM_FIRST // other variables start from here , SCRIPT_VAR_CUSTOM_LOTS = 0xFFFFFF // ensure that sizeof(Variable) is large enough }; diff --git a/src/util/io/package_manager.cpp b/src/util/io/package_manager.cpp index 63fb0e35..60683c5f 100644 --- a/src/util/io/package_manager.cpp +++ b/src/util/io/package_manager.cpp @@ -120,6 +120,27 @@ InputStreamP PackageManager::openFileFromPackage(Packaged*& package, const Strin throw FileNotFoundError(name, _("No package name specified, use '/package/filename'")); } +String PackageManager::openFilenameFromPackage(Packaged*& package, const String& name) { + if (!name.empty() && name.GetChar(0) == _('/')) { + // absolute name; break name + size_t start = name.find_first_not_of(_("/\\"), 1); // allow "//package/name" from incorrect scripts + size_t pos = name.find_first_of(_("/\\"), start); + if (start < pos && pos != String::npos) { + // open package + PackagedP p = openAny(name.substr(start, pos-start)); + if (package && !is_substr(name,start,_(":NO-WARN-DEP:"))) { + package->requireDependency(p.get()); + } + package = p.get(); + return p->absoluteFilename() + _("/") + name.substr(pos + 1); + } + } else if (package) { + // relative name + return package->absoluteFilename() + _("/") + name; + } + throw FileNotFoundError(name, _("No package name specified, use '/package/filename'")); +} + String PackageManager::getDictionaryDir(bool l) const { String dir = (l ? local : global).getDirectory(); if (dir.empty()) return wxEmptyString; diff --git a/src/util/io/package_manager.hpp b/src/util/io/package_manager.hpp index f0b72584..f288b3fd 100644 --- a/src/util/io/package_manager.hpp +++ b/src/util/io/package_manager.hpp @@ -150,6 +150,12 @@ class PackageManager { */ InputStreamP openFileFromPackage(Packaged*& package, const String& name); + /// Get a filename to open from a package + /** WARNING: this is a bit of a hack, since not all package types support names in this way. + * It is needed for third party libraries (i.e. hunspell) that load stuff from files. + */ + String openFilenameFromPackage(Packaged*& package, const String& name); + // --------------------------------------------------- : Packages on disk /// Check if the given dependency is currently installed diff --git a/src/util/spell_checker.cpp b/src/util/spell_checker.cpp index 125d0dae..6b8c8e9c 100644 --- a/src/util/spell_checker.cpp +++ b/src/util/spell_checker.cpp @@ -35,6 +35,31 @@ SpellChecker& SpellChecker::get(const String& language) { return *speller; } +SpellChecker& SpellChecker::get(const String& filename, const String& language) { + SpellCheckerP& speller = spellers[filename + _(".") + language]; + if (!speller) { + Packaged* package = nullptr; + String prefix = package_manager.openFilenameFromPackage(package, filename) + _("."); + String local_dir = package_manager.getDictionaryDir(true); + String global_dir = package_manager.getDictionaryDir(false); + String aff_path = language + _(".aff"); + String dic_path = language + _(".dic"); + if (wxFileExists(prefix + aff_path) && wxFileExists(prefix + dic_path)) { + speller = SpellCheckerP(new SpellChecker((prefix + aff_path).mb_str(), + (prefix + dic_path).mb_str())); + } else if (wxFileExists(local_dir + aff_path) && wxFileExists(prefix + dic_path)) { + speller = SpellCheckerP(new SpellChecker((local_dir + aff_path).mb_str(), + (prefix + dic_path).mb_str())); + } else if (wxFileExists(global_dir + aff_path) && wxFileExists(prefix + dic_path)) { + speller = SpellCheckerP(new SpellChecker((global_dir + aff_path).mb_str(), + (prefix + dic_path).mb_str())); + } else { + throw Error(_("Dictionary '") + filename + _("' not found for language: ") + language); + } + } + return *speller; +} + SpellChecker::SpellChecker(const char* aff_path, const char* dic_path) : Hunspell(aff_path,dic_path) , encoding(String(get_dic_encoding(), IF_UNICODE(wxConvLibc, wxSTRING_MAXLEN))) diff --git a/src/util/spell_checker.hpp b/src/util/spell_checker.hpp index e2831721..52ffae0b 100644 --- a/src/util/spell_checker.hpp +++ b/src/util/spell_checker.hpp @@ -22,6 +22,9 @@ class SpellChecker : public Hunspell, public IntrusivePtrBase { /// Get a SpellChecker object for the given language. /** Note: This is not threadsafe yet */ static SpellChecker& get(const String& language); + /// Get a SpellChecker object for the given language and filename + /** Note: This is not threadsafe yet */ + static SpellChecker& get(const String& filename, const String& language); /// Destroy all cached SpellChecker objects static void destroyAll();