Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/slic3r/Utils')
-rw-r--r--src/slic3r/Utils/ASCIIFolding.cpp1954
-rw-r--r--src/slic3r/Utils/ASCIIFolding.hpp15
-rw-r--r--src/slic3r/Utils/Bonjour.cpp781
-rw-r--r--src/slic3r/Utils/Bonjour.hpp64
-rw-r--r--src/slic3r/Utils/Duet.cpp279
-rw-r--r--src/slic3r/Utils/Duet.hpp47
-rw-r--r--src/slic3r/Utils/FixModelByWin10.cpp402
-rw-r--r--src/slic3r/Utils/FixModelByWin10.hpp26
-rw-r--r--src/slic3r/Utils/HexFile.cpp106
-rw-r--r--src/slic3r/Utils/HexFile.hpp33
-rw-r--r--src/slic3r/Utils/Http.cpp451
-rw-r--r--src/slic3r/Utils/Http.hpp115
-rw-r--r--src/slic3r/Utils/OctoPrint.cpp173
-rw-r--r--src/slic3r/Utils/OctoPrint.hpp42
-rw-r--r--src/slic3r/Utils/PresetUpdater.cpp633
-rw-r--r--src/slic3r/Utils/PresetUpdater.hpp42
-rw-r--r--src/slic3r/Utils/PrintHost.cpp23
-rw-r--r--src/slic3r/Utils/PrintHost.hpp35
-rw-r--r--src/slic3r/Utils/PrintHostSendDialog.cpp52
-rw-r--r--src/slic3r/Utils/PrintHostSendDialog.hpp38
-rw-r--r--src/slic3r/Utils/Semver.hpp151
-rw-r--r--src/slic3r/Utils/Serial.cpp495
-rw-r--r--src/slic3r/Utils/Serial.hpp82
-rw-r--r--src/slic3r/Utils/Time.cpp80
-rw-r--r--src/slic3r/Utils/Time.hpp25
25 files changed, 6144 insertions, 0 deletions
diff --git a/src/slic3r/Utils/ASCIIFolding.cpp b/src/slic3r/Utils/ASCIIFolding.cpp
new file mode 100644
index 000000000..c61fe2902
--- /dev/null
+++ b/src/slic3r/Utils/ASCIIFolding.cpp
@@ -0,0 +1,1954 @@
+#include "ASCIIFolding.hpp"
+
+#include <stdio.h>
+#include <string.h>
+#include <locale>
+#include <boost/locale/encoding_utf.hpp>
+
+// Based on http://svn.apache.org/repos/asf/lucene/java/tags/lucene_solr_4_5_1/lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/ASCIIFoldingFilter.java
+template<typename OUTPUT_ITERATOR>
+static void fold_to_ascii(wchar_t c, OUTPUT_ITERATOR out)
+{
+ if (c < 0x080) {
+ *out = c;
+ } else {
+ switch (c) {
+ case L'\u00C0': // [LATIN CAPITAL LETTER A WITH GRAVE]
+ case L'\u00C1': // [LATIN CAPITAL LETTER A WITH ACUTE]
+ case L'\u00C2': // [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
+ case L'\u00C3': // [LATIN CAPITAL LETTER A WITH TILDE]
+ case L'\u00C4': // [LATIN CAPITAL LETTER A WITH DIAERESIS]
+ case L'\u00C5': // [LATIN CAPITAL LETTER A WITH RING ABOVE]
+ case L'\u0100': // [LATIN CAPITAL LETTER A WITH MACRON]
+ case L'\u0102': // [LATIN CAPITAL LETTER A WITH BREVE]
+ case L'\u0104': // [LATIN CAPITAL LETTER A WITH OGONEK]
+ case L'\u018F': // [LATIN CAPITAL LETTER SCHWA]
+ case L'\u01CD': // [LATIN CAPITAL LETTER A WITH CARON]
+ case L'\u01DE': // [LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON]
+ case L'\u01E0': // [LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON]
+ case L'\u01FA': // [LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE]
+ case L'\u0200': // [LATIN CAPITAL LETTER A WITH DOUBLE GRAVE]
+ case L'\u0202': // [LATIN CAPITAL LETTER A WITH INVERTED BREVE]
+ case L'\u0226': // [LATIN CAPITAL LETTER A WITH DOT ABOVE]
+ case L'\u023A': // [LATIN CAPITAL LETTER A WITH STROKE]
+ case L'\u1D00': // [LATIN LETTER SMALL CAPITAL A]
+ case L'\u1E00': // [LATIN CAPITAL LETTER A WITH RING BELOW]
+ case L'\u1EA0': // [LATIN CAPITAL LETTER A WITH DOT BELOW]
+ case L'\u1EA2': // [LATIN CAPITAL LETTER A WITH HOOK ABOVE]
+ case L'\u1EA4': // [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE]
+ case L'\u1EA6': // [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE]
+ case L'\u1EA8': // [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE]
+ case L'\u1EAA': // [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE]
+ case L'\u1EAC': // [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW]
+ case L'\u1EAE': // [LATIN CAPITAL LETTER A WITH BREVE AND ACUTE]
+ case L'\u1EB0': // [LATIN CAPITAL LETTER A WITH BREVE AND GRAVE]
+ case L'\u1EB2': // [LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE]
+ case L'\u1EB4': // [LATIN CAPITAL LETTER A WITH BREVE AND TILDE]
+ case L'\u1EB6': // [LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW]
+ case L'\u24B6': // [CIRCLED LATIN CAPITAL LETTER A]
+ case L'\uFF21': // [FULLWIDTH LATIN CAPITAL LETTER A]
+ *out = 'A';
+ break;
+ case L'\u00E0': // [LATIN SMALL LETTER A WITH GRAVE]
+ case L'\u00E1': // [LATIN SMALL LETTER A WITH ACUTE]
+ case L'\u00E2': // [LATIN SMALL LETTER A WITH CIRCUMFLEX]
+ case L'\u00E3': // [LATIN SMALL LETTER A WITH TILDE]
+ case L'\u00E4': // [LATIN SMALL LETTER A WITH DIAERESIS]
+ case L'\u00E5': // [LATIN SMALL LETTER A WITH RING ABOVE]
+ case L'\u0101': // [LATIN SMALL LETTER A WITH MACRON]
+ case L'\u0103': // [LATIN SMALL LETTER A WITH BREVE]
+ case L'\u0105': // [LATIN SMALL LETTER A WITH OGONEK]
+ case L'\u01CE': // [LATIN SMALL LETTER A WITH CARON]
+ case L'\u01DF': // [LATIN SMALL LETTER A WITH DIAERESIS AND MACRON]
+ case L'\u01E1': // [LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON]
+ case L'\u01FB': // [LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE]
+ case L'\u0201': // [LATIN SMALL LETTER A WITH DOUBLE GRAVE]
+ case L'\u0203': // [LATIN SMALL LETTER A WITH INVERTED BREVE]
+ case L'\u0227': // [LATIN SMALL LETTER A WITH DOT ABOVE]
+ case L'\u0250': // [LATIN SMALL LETTER TURNED A]
+ case L'\u0259': // [LATIN SMALL LETTER SCHWA]
+ case L'\u025A': // [LATIN SMALL LETTER SCHWA WITH HOOK]
+ case L'\u1D8F': // [LATIN SMALL LETTER A WITH RETROFLEX HOOK]
+ case L'\u1D95': // [LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK]
+ case L'\u1E01': // [LATIN SMALL LETTER A WITH RING BELOW]
+ case L'\u1E9A': // [LATIN SMALL LETTER A WITH RIGHT HALF RING]
+ case L'\u1EA1': // [LATIN SMALL LETTER A WITH DOT BELOW]
+ case L'\u1EA3': // [LATIN SMALL LETTER A WITH HOOK ABOVE]
+ case L'\u1EA5': // [LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE]
+ case L'\u1EA7': // [LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE]
+ case L'\u1EA9': // [LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE]
+ case L'\u1EAB': // [LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE]
+ case L'\u1EAD': // [LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW]
+ case L'\u1EAF': // [LATIN SMALL LETTER A WITH BREVE AND ACUTE]
+ case L'\u1EB1': // [LATIN SMALL LETTER A WITH BREVE AND GRAVE]
+ case L'\u1EB3': // [LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE]
+ case L'\u1EB5': // [LATIN SMALL LETTER A WITH BREVE AND TILDE]
+ case L'\u1EB7': // [LATIN SMALL LETTER A WITH BREVE AND DOT BELOW]
+ case L'\u2090': // [LATIN SUBSCRIPT SMALL LETTER A]
+ case L'\u2094': // [LATIN SUBSCRIPT SMALL LETTER SCHWA]
+ case L'\u24D0': // [CIRCLED LATIN SMALL LETTER A]
+ case L'\u2C65': // [LATIN SMALL LETTER A WITH STROKE]
+ case L'\u2C6F': // [LATIN CAPITAL LETTER TURNED A]
+ case L'\uFF41': // [FULLWIDTH LATIN SMALL LETTER A]
+ *out = 'a';
+ break;
+ case L'\uA732': // [LATIN CAPITAL LETTER AA]
+ *out = 'A';
+ *out = 'A';
+ break;
+ case L'\u00C6': // [LATIN CAPITAL LETTER AE]
+ case L'\u01E2': // [LATIN CAPITAL LETTER AE WITH MACRON]
+ case L'\u01FC': // [LATIN CAPITAL LETTER AE WITH ACUTE]
+ case L'\u1D01': // [LATIN LETTER SMALL CAPITAL AE]
+ *out = 'A';
+ *out = 'E';
+ break;
+ case L'\uA734': // [LATIN CAPITAL LETTER AO]
+ *out = 'A';
+ *out = 'O';
+ break;
+ case L'\uA736': // [LATIN CAPITAL LETTER AU]
+ *out = 'A';
+ *out = 'U';
+ break;
+ case L'\uA738': // [LATIN CAPITAL LETTER AV]
+ case L'\uA73A': // [LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR]
+ *out = 'A';
+ *out = 'V';
+ break;
+ case L'\uA73C': // [LATIN CAPITAL LETTER AY]
+ *out = 'A';
+ *out = 'Y';
+ break;
+ case L'\u249C': // [PARENTHESIZED LATIN SMALL LETTER A]
+ *out = '(';
+ *out = 'a';
+ *out = ')';
+ break;
+ case L'\uA733': // [LATIN SMALL LETTER AA]
+ *out = 'a';
+ *out = 'a';
+ break;
+ case L'\u00E6': // [LATIN SMALL LETTER AE]
+ case L'\u01E3': // [LATIN SMALL LETTER AE WITH MACRON]
+ case L'\u01FD': // [LATIN SMALL LETTER AE WITH ACUTE]
+ case L'\u1D02': // [LATIN SMALL LETTER TURNED AE]
+ *out = 'a';
+ *out = 'e';
+ break;
+ case L'\uA735': // [LATIN SMALL LETTER AO]
+ *out = 'a';
+ *out = 'o';
+ break;
+ case L'\uA737': // [LATIN SMALL LETTER AU]
+ *out = 'a';
+ *out = 'u';
+ break;
+ case L'\uA739': // [LATIN SMALL LETTER AV]
+ case L'\uA73B': // [LATIN SMALL LETTER AV WITH HORIZONTAL BAR]
+ *out = 'a';
+ *out = 'v';
+ break;
+ case L'\uA73D': // [LATIN SMALL LETTER AY]
+ *out = 'a';
+ *out = 'y';
+ break;
+ case L'\u0181': // [LATIN CAPITAL LETTER B WITH HOOK]
+ case L'\u0182': // [LATIN CAPITAL LETTER B WITH TOPBAR]
+ case L'\u0243': // [LATIN CAPITAL LETTER B WITH STROKE]
+ case L'\u0299': // [LATIN LETTER SMALL CAPITAL B]
+ case L'\u1D03': // [LATIN LETTER SMALL CAPITAL BARRED B]
+ case L'\u1E02': // [LATIN CAPITAL LETTER B WITH DOT ABOVE]
+ case L'\u1E04': // [LATIN CAPITAL LETTER B WITH DOT BELOW]
+ case L'\u1E06': // [LATIN CAPITAL LETTER B WITH LINE BELOW]
+ case L'\u24B7': // [CIRCLED LATIN CAPITAL LETTER B]
+ case L'\uFF22': // [FULLWIDTH LATIN CAPITAL LETTER B]
+ *out = 'B';
+ break;
+ case L'\u0180': // [LATIN SMALL LETTER B WITH STROKE]
+ case L'\u0183': // [LATIN SMALL LETTER B WITH TOPBAR]
+ case L'\u0253': // [LATIN SMALL LETTER B WITH HOOK]
+ case L'\u1D6C': // [LATIN SMALL LETTER B WITH MIDDLE TILDE]
+ case L'\u1D80': // [LATIN SMALL LETTER B WITH PALATAL HOOK]
+ case L'\u1E03': // [LATIN SMALL LETTER B WITH DOT ABOVE]
+ case L'\u1E05': // [LATIN SMALL LETTER B WITH DOT BELOW]
+ case L'\u1E07': // [LATIN SMALL LETTER B WITH LINE BELOW]
+ case L'\u24D1': // [CIRCLED LATIN SMALL LETTER B]
+ case L'\uFF42': // [FULLWIDTH LATIN SMALL LETTER B]
+ *out = 'b';
+ break;
+ case L'\u249D': // [PARENTHESIZED LATIN SMALL LETTER B]
+ *out = '(';
+ *out = 'b';
+ *out = ')';
+ break;
+ case L'\u00C7': // [LATIN CAPITAL LETTER C WITH CEDILLA]
+ case L'\u0106': // [LATIN CAPITAL LETTER C WITH ACUTE]
+ case L'\u0108': // [LATIN CAPITAL LETTER C WITH CIRCUMFLEX]
+ case L'\u010A': // [LATIN CAPITAL LETTER C WITH DOT ABOVE]
+ case L'\u010C': // [LATIN CAPITAL LETTER C WITH CARON]
+ case L'\u0187': // [LATIN CAPITAL LETTER C WITH HOOK]
+ case L'\u023B': // [LATIN CAPITAL LETTER C WITH STROKE]
+ case L'\u0297': // [LATIN LETTER STRETCHED C]
+ case L'\u1D04': // [LATIN LETTER SMALL CAPITAL C]
+ case L'\u1E08': // [LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE]
+ case L'\u24B8': // [CIRCLED LATIN CAPITAL LETTER C]
+ case L'\uFF23': // [FULLWIDTH LATIN CAPITAL LETTER C]
+ *out = 'C';
+ break;
+ case L'\u00E7': // [LATIN SMALL LETTER C WITH CEDILLA]
+ case L'\u0107': // [LATIN SMALL LETTER C WITH ACUTE]
+ case L'\u0109': // [LATIN SMALL LETTER C WITH CIRCUMFLEX]
+ case L'\u010B': // [LATIN SMALL LETTER C WITH DOT ABOVE]
+ case L'\u010D': // [LATIN SMALL LETTER C WITH CARON]
+ case L'\u0188': // [LATIN SMALL LETTER C WITH HOOK]
+ case L'\u023C': // [LATIN SMALL LETTER C WITH STROKE]
+ case L'\u0255': // [LATIN SMALL LETTER C WITH CURL]
+ case L'\u1E09': // [LATIN SMALL LETTER C WITH CEDILLA AND ACUTE]
+ case L'\u2184': // [LATIN SMALL LETTER REVERSED C]
+ case L'\u24D2': // [CIRCLED LATIN SMALL LETTER C]
+ case L'\uA73E': // [LATIN CAPITAL LETTER REVERSED C WITH DOT]
+ case L'\uA73F': // [LATIN SMALL LETTER REVERSED C WITH DOT]
+ case L'\uFF43': // [FULLWIDTH LATIN SMALL LETTER C]
+ *out = 'c';
+ break;
+ case L'\u249E': // [PARENTHESIZED LATIN SMALL LETTER C]
+ *out = '(';
+ *out = 'c';
+ *out = ')';
+ break;
+ case L'\u00D0': // [LATIN CAPITAL LETTER ETH]
+ case L'\u010E': // [LATIN CAPITAL LETTER D WITH CARON]
+ case L'\u0110': // [LATIN CAPITAL LETTER D WITH STROKE]
+ case L'\u0189': // [LATIN CAPITAL LETTER AFRICAN D]
+ case L'\u018A': // [LATIN CAPITAL LETTER D WITH HOOK]
+ case L'\u018B': // [LATIN CAPITAL LETTER D WITH TOPBAR]
+ case L'\u1D05': // [LATIN LETTER SMALL CAPITAL D]
+ case L'\u1D06': // [LATIN LETTER SMALL CAPITAL ETH]
+ case L'\u1E0A': // [LATIN CAPITAL LETTER D WITH DOT ABOVE]
+ case L'\u1E0C': // [LATIN CAPITAL LETTER D WITH DOT BELOW]
+ case L'\u1E0E': // [LATIN CAPITAL LETTER D WITH LINE BELOW]
+ case L'\u1E10': // [LATIN CAPITAL LETTER D WITH CEDILLA]
+ case L'\u1E12': // [LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW]
+ case L'\u24B9': // [CIRCLED LATIN CAPITAL LETTER D]
+ case L'\uA779': // [LATIN CAPITAL LETTER INSULAR D]
+ case L'\uFF24': // [FULLWIDTH LATIN CAPITAL LETTER D]
+ *out = 'D';
+ break;
+ case L'\u00F0': // [LATIN SMALL LETTER ETH]
+ case L'\u010F': // [LATIN SMALL LETTER D WITH CARON]
+ case L'\u0111': // [LATIN SMALL LETTER D WITH STROKE]
+ case L'\u018C': // [LATIN SMALL LETTER D WITH TOPBAR]
+ case L'\u0221': // [LATIN SMALL LETTER D WITH CURL]
+ case L'\u0256': // [LATIN SMALL LETTER D WITH TAIL]
+ case L'\u0257': // [LATIN SMALL LETTER D WITH HOOK]
+ case L'\u1D6D': // [LATIN SMALL LETTER D WITH MIDDLE TILDE]
+ case L'\u1D81': // [LATIN SMALL LETTER D WITH PALATAL HOOK]
+ case L'\u1D91': // [LATIN SMALL LETTER D WITH HOOK AND TAIL]
+ case L'\u1E0B': // [LATIN SMALL LETTER D WITH DOT ABOVE]
+ case L'\u1E0D': // [LATIN SMALL LETTER D WITH DOT BELOW]
+ case L'\u1E0F': // [LATIN SMALL LETTER D WITH LINE BELOW]
+ case L'\u1E11': // [LATIN SMALL LETTER D WITH CEDILLA]
+ case L'\u1E13': // [LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW]
+ case L'\u24D3': // [CIRCLED LATIN SMALL LETTER D]
+ case L'\uA77A': // [LATIN SMALL LETTER INSULAR D]
+ case L'\uFF44': // [FULLWIDTH LATIN SMALL LETTER D]
+ *out = 'd';
+ break;
+ case L'\u01C4': // [LATIN CAPITAL LETTER DZ WITH CARON]
+ case L'\u01F1': // [LATIN CAPITAL LETTER DZ]
+ *out = 'D';
+ *out = 'Z';
+ break;
+ case L'\u01C5': // [LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON]
+ case L'\u01F2': // [LATIN CAPITAL LETTER D WITH SMALL LETTER Z]
+ *out = 'D';
+ *out = 'z';
+ break;
+ case L'\u249F': // [PARENTHESIZED LATIN SMALL LETTER D]
+ *out = '(';
+ *out = 'd';
+ *out = ')';
+ break;
+ case L'\u0238': // [LATIN SMALL LETTER DB DIGRAPH]
+ *out = 'd';
+ *out = 'b';
+ break;
+ case L'\u01C6': // [LATIN SMALL LETTER DZ WITH CARON]
+ case L'\u01F3': // [LATIN SMALL LETTER DZ]
+ case L'\u02A3': // [LATIN SMALL LETTER DZ DIGRAPH]
+ case L'\u02A5': // [LATIN SMALL LETTER DZ DIGRAPH WITH CURL]
+ *out = 'd';
+ *out = 'z';
+ break;
+ case L'\u00C8': // [LATIN CAPITAL LETTER E WITH GRAVE]
+ case L'\u00C9': // [LATIN CAPITAL LETTER E WITH ACUTE]
+ case L'\u00CA': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX]
+ case L'\u00CB': // [LATIN CAPITAL LETTER E WITH DIAERESIS]
+ case L'\u0112': // [LATIN CAPITAL LETTER E WITH MACRON]
+ case L'\u0114': // [LATIN CAPITAL LETTER E WITH BREVE]
+ case L'\u0116': // [LATIN CAPITAL LETTER E WITH DOT ABOVE]
+ case L'\u0118': // [LATIN CAPITAL LETTER E WITH OGONEK]
+ case L'\u011A': // [LATIN CAPITAL LETTER E WITH CARON]
+ case L'\u018E': // [LATIN CAPITAL LETTER REVERSED E]
+ case L'\u0190': // [LATIN CAPITAL LETTER OPEN E]
+ case L'\u0204': // [LATIN CAPITAL LETTER E WITH DOUBLE GRAVE]
+ case L'\u0206': // [LATIN CAPITAL LETTER E WITH INVERTED BREVE]
+ case L'\u0228': // [LATIN CAPITAL LETTER E WITH CEDILLA]
+ case L'\u0246': // [LATIN CAPITAL LETTER E WITH STROKE]
+ case L'\u1D07': // [LATIN LETTER SMALL CAPITAL E]
+ case L'\u1E14': // [LATIN CAPITAL LETTER E WITH MACRON AND GRAVE]
+ case L'\u1E16': // [LATIN CAPITAL LETTER E WITH MACRON AND ACUTE]
+ case L'\u1E18': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW]
+ case L'\u1E1A': // [LATIN CAPITAL LETTER E WITH TILDE BELOW]
+ case L'\u1E1C': // [LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE]
+ case L'\u1EB8': // [LATIN CAPITAL LETTER E WITH DOT BELOW]
+ case L'\u1EBA': // [LATIN CAPITAL LETTER E WITH HOOK ABOVE]
+ case L'\u1EBC': // [LATIN CAPITAL LETTER E WITH TILDE]
+ case L'\u1EBE': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE]
+ case L'\u1EC0': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE]
+ case L'\u1EC2': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE]
+ case L'\u1EC4': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE]
+ case L'\u1EC6': // [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW]
+ case L'\u24BA': // [CIRCLED LATIN CAPITAL LETTER E]
+ case L'\u2C7B': // [LATIN LETTER SMALL CAPITAL TURNED E]
+ case L'\uFF25': // [FULLWIDTH LATIN CAPITAL LETTER E]
+ *out = 'E';
+ break;
+ case L'\u00E8': // [LATIN SMALL LETTER E WITH GRAVE]
+ case L'\u00E9': // [LATIN SMALL LETTER E WITH ACUTE]
+ case L'\u00EA': // [LATIN SMALL LETTER E WITH CIRCUMFLEX]
+ case L'\u00EB': // [LATIN SMALL LETTER E WITH DIAERESIS]
+ case L'\u0113': // [LATIN SMALL LETTER E WITH MACRON]
+ case L'\u0115': // [LATIN SMALL LETTER E WITH BREVE]
+ case L'\u0117': // [LATIN SMALL LETTER E WITH DOT ABOVE]
+ case L'\u0119': // [LATIN SMALL LETTER E WITH OGONEK]
+ case L'\u011B': // [LATIN SMALL LETTER E WITH CARON]
+ case L'\u01DD': // [LATIN SMALL LETTER TURNED E]
+ case L'\u0205': // [LATIN SMALL LETTER E WITH DOUBLE GRAVE]
+ case L'\u0207': // [LATIN SMALL LETTER E WITH INVERTED BREVE]
+ case L'\u0229': // [LATIN SMALL LETTER E WITH CEDILLA]
+ case L'\u0247': // [LATIN SMALL LETTER E WITH STROKE]
+ case L'\u0258': // [LATIN SMALL LETTER REVERSED E]
+ case L'\u025B': // [LATIN SMALL LETTER OPEN E]
+ case L'\u025C': // [LATIN SMALL LETTER REVERSED OPEN E]
+ case L'\u025D': // [LATIN SMALL LETTER REVERSED OPEN E WITH HOOK]
+ case L'\u025E': // [LATIN SMALL LETTER CLOSED REVERSED OPEN E]
+ case L'\u029A': // [LATIN SMALL LETTER CLOSED OPEN E]
+ case L'\u1D08': // [LATIN SMALL LETTER TURNED OPEN E]
+ case L'\u1D92': // [LATIN SMALL LETTER E WITH RETROFLEX HOOK]
+ case L'\u1D93': // [LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK]
+ case L'\u1D94': // [LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK]
+ case L'\u1E15': // [LATIN SMALL LETTER E WITH MACRON AND GRAVE]
+ case L'\u1E17': // [LATIN SMALL LETTER E WITH MACRON AND ACUTE]
+ case L'\u1E19': // [LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW]
+ case L'\u1E1B': // [LATIN SMALL LETTER E WITH TILDE BELOW]
+ case L'\u1E1D': // [LATIN SMALL LETTER E WITH CEDILLA AND BREVE]
+ case L'\u1EB9': // [LATIN SMALL LETTER E WITH DOT BELOW]
+ case L'\u1EBB': // [LATIN SMALL LETTER E WITH HOOK ABOVE]
+ case L'\u1EBD': // [LATIN SMALL LETTER E WITH TILDE]
+ case L'\u1EBF': // [LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE]
+ case L'\u1EC1': // [LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE]
+ case L'\u1EC3': // [LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE]
+ case L'\u1EC5': // [LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE]
+ case L'\u1EC7': // [LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW]
+ case L'\u2091': // [LATIN SUBSCRIPT SMALL LETTER E]
+ case L'\u24D4': // [CIRCLED LATIN SMALL LETTER E]
+ case L'\u2C78': // [LATIN SMALL LETTER E WITH NOTCH]
+ case L'\uFF45': // [FULLWIDTH LATIN SMALL LETTER E]
+ *out = 'e';
+ break;
+ case L'\u24A0': // [PARENTHESIZED LATIN SMALL LETTER E]
+ *out = '(';
+ *out = 'e';
+ *out = ')';
+ break;
+ case L'\u0191': // [LATIN CAPITAL LETTER F WITH HOOK]
+ case L'\u1E1E': // [LATIN CAPITAL LETTER F WITH DOT ABOVE]
+ case L'\u24BB': // [CIRCLED LATIN CAPITAL LETTER F]
+ case L'\uA730': // [LATIN LETTER SMALL CAPITAL F]
+ case L'\uA77B': // [LATIN CAPITAL LETTER INSULAR F]
+ case L'\uA7FB': // [LATIN EPIGRAPHIC LETTER REVERSED F]
+ case L'\uFF26': // [FULLWIDTH LATIN CAPITAL LETTER F]
+ *out = 'F';
+ break;
+ case L'\u0192': // [LATIN SMALL LETTER F WITH HOOK]
+ case L'\u1D6E': // [LATIN SMALL LETTER F WITH MIDDLE TILDE]
+ case L'\u1D82': // [LATIN SMALL LETTER F WITH PALATAL HOOK]
+ case L'\u1E1F': // [LATIN SMALL LETTER F WITH DOT ABOVE]
+ case L'\u1E9B': // [LATIN SMALL LETTER LONG S WITH DOT ABOVE]
+ case L'\u24D5': // [CIRCLED LATIN SMALL LETTER F]
+ case L'\uA77C': // [LATIN SMALL LETTER INSULAR F]
+ case L'\uFF46': // [FULLWIDTH LATIN SMALL LETTER F]
+ *out = 'f';
+ break;
+ case L'\u24A1': // [PARENTHESIZED LATIN SMALL LETTER F]
+ *out = '(';
+ *out = 'f';
+ *out = ')';
+ break;
+ case L'\uFB00': // [LATIN SMALL LIGATURE FF]
+ *out = 'f';
+ *out = 'f';
+ break;
+ case L'\uFB03': // [LATIN SMALL LIGATURE FFI]
+ *out = 'f';
+ *out = 'f';
+ *out = 'i';
+ break;
+ case L'\uFB04': // [LATIN SMALL LIGATURE FFL]
+ *out = 'f';
+ *out = 'f';
+ *out = 'l';
+ break;
+ case L'\uFB01': // [LATIN SMALL LIGATURE FI]
+ *out = 'f';
+ *out = 'i';
+ break;
+ case L'\uFB02': // [LATIN SMALL LIGATURE FL]
+ *out = 'f';
+ *out = 'l';
+ break;
+ case L'\u011C': // [LATIN CAPITAL LETTER G WITH CIRCUMFLEX]
+ case L'\u011E': // [LATIN CAPITAL LETTER G WITH BREVE]
+ case L'\u0120': // [LATIN CAPITAL LETTER G WITH DOT ABOVE]
+ case L'\u0122': // [LATIN CAPITAL LETTER G WITH CEDILLA]
+ case L'\u0193': // [LATIN CAPITAL LETTER G WITH HOOK]
+ case L'\u01E4': // [LATIN CAPITAL LETTER G WITH STROKE]
+ case L'\u01E5': // [LATIN SMALL LETTER G WITH STROKE]
+ case L'\u01E6': // [LATIN CAPITAL LETTER G WITH CARON]
+ case L'\u01E7': // [LATIN SMALL LETTER G WITH CARON]
+ case L'\u01F4': // [LATIN CAPITAL LETTER G WITH ACUTE]
+ case L'\u0262': // [LATIN LETTER SMALL CAPITAL G]
+ case L'\u029B': // [LATIN LETTER SMALL CAPITAL G WITH HOOK]
+ case L'\u1E20': // [LATIN CAPITAL LETTER G WITH MACRON]
+ case L'\u24BC': // [CIRCLED LATIN CAPITAL LETTER G]
+ case L'\uA77D': // [LATIN CAPITAL LETTER INSULAR G]
+ case L'\uA77E': // [LATIN CAPITAL LETTER TURNED INSULAR G]
+ case L'\uFF27': // [FULLWIDTH LATIN CAPITAL LETTER G]
+ *out = 'G';
+ break;
+ case L'\u011D': // [LATIN SMALL LETTER G WITH CIRCUMFLEX]
+ case L'\u011F': // [LATIN SMALL LETTER G WITH BREVE]
+ case L'\u0121': // [LATIN SMALL LETTER G WITH DOT ABOVE]
+ case L'\u0123': // [LATIN SMALL LETTER G WITH CEDILLA]
+ case L'\u01F5': // [LATIN SMALL LETTER G WITH ACUTE]
+ case L'\u0260': // [LATIN SMALL LETTER G WITH HOOK]
+ case L'\u0261': // [LATIN SMALL LETTER SCRIPT G]
+ case L'\u1D77': // [LATIN SMALL LETTER TURNED G]
+ case L'\u1D79': // [LATIN SMALL LETTER INSULAR G]
+ case L'\u1D83': // [LATIN SMALL LETTER G WITH PALATAL HOOK]
+ case L'\u1E21': // [LATIN SMALL LETTER G WITH MACRON]
+ case L'\u24D6': // [CIRCLED LATIN SMALL LETTER G]
+ case L'\uA77F': // [LATIN SMALL LETTER TURNED INSULAR G]
+ case L'\uFF47': // [FULLWIDTH LATIN SMALL LETTER G]
+ *out = 'g';
+ break;
+ case L'\u24A2': // [PARENTHESIZED LATIN SMALL LETTER G]
+ *out = '(';
+ *out = 'g';
+ *out = ')';
+ break;
+ case L'\u0124': // [LATIN CAPITAL LETTER H WITH CIRCUMFLEX]
+ case L'\u0126': // [LATIN CAPITAL LETTER H WITH STROKE]
+ case L'\u021E': // [LATIN CAPITAL LETTER H WITH CARON]
+ case L'\u029C': // [LATIN LETTER SMALL CAPITAL H]
+ case L'\u1E22': // [LATIN CAPITAL LETTER H WITH DOT ABOVE]
+ case L'\u1E24': // [LATIN CAPITAL LETTER H WITH DOT BELOW]
+ case L'\u1E26': // [LATIN CAPITAL LETTER H WITH DIAERESIS]
+ case L'\u1E28': // [LATIN CAPITAL LETTER H WITH CEDILLA]
+ case L'\u1E2A': // [LATIN CAPITAL LETTER H WITH BREVE BELOW]
+ case L'\u24BD': // [CIRCLED LATIN CAPITAL LETTER H]
+ case L'\u2C67': // [LATIN CAPITAL LETTER H WITH DESCENDER]
+ case L'\u2C75': // [LATIN CAPITAL LETTER HALF H]
+ case L'\uFF28': // [FULLWIDTH LATIN CAPITAL LETTER H]
+ *out = 'H';
+ break;
+ case L'\u0125': // [LATIN SMALL LETTER H WITH CIRCUMFLEX]
+ case L'\u0127': // [LATIN SMALL LETTER H WITH STROKE]
+ case L'\u021F': // [LATIN SMALL LETTER H WITH CARON]
+ case L'\u0265': // [LATIN SMALL LETTER TURNED H]
+ case L'\u0266': // [LATIN SMALL LETTER H WITH HOOK]
+ case L'\u02AE': // [LATIN SMALL LETTER TURNED H WITH FISHHOOK]
+ case L'\u02AF': // [LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL]
+ case L'\u1E23': // [LATIN SMALL LETTER H WITH DOT ABOVE]
+ case L'\u1E25': // [LATIN SMALL LETTER H WITH DOT BELOW]
+ case L'\u1E27': // [LATIN SMALL LETTER H WITH DIAERESIS]
+ case L'\u1E29': // [LATIN SMALL LETTER H WITH CEDILLA]
+ case L'\u1E2B': // [LATIN SMALL LETTER H WITH BREVE BELOW]
+ case L'\u1E96': // [LATIN SMALL LETTER H WITH LINE BELOW]
+ case L'\u24D7': // [CIRCLED LATIN SMALL LETTER H]
+ case L'\u2C68': // [LATIN SMALL LETTER H WITH DESCENDER]
+ case L'\u2C76': // [LATIN SMALL LETTER HALF H]
+ case L'\uFF48': // [FULLWIDTH LATIN SMALL LETTER H]
+ *out = 'h';
+ break;
+ case L'\u01F6': // [LATIN CAPITAL LETTER HWAIR]
+ *out = 'H';
+ *out = 'V';
+ break;
+ case L'\u24A3': // [PARENTHESIZED LATIN SMALL LETTER H]
+ *out = '(';
+ *out = 'h';
+ *out = ')';
+ break;
+ case L'\u0195': // [LATIN SMALL LETTER HV]
+ *out = 'h';
+ *out = 'v';
+ break;
+ case L'\u00CC': // [LATIN CAPITAL LETTER I WITH GRAVE]
+ case L'\u00CD': // [LATIN CAPITAL LETTER I WITH ACUTE]
+ case L'\u00CE': // [LATIN CAPITAL LETTER I WITH CIRCUMFLEX]
+ case L'\u00CF': // [LATIN CAPITAL LETTER I WITH DIAERESIS]
+ case L'\u0128': // [LATIN CAPITAL LETTER I WITH TILDE]
+ case L'\u012A': // [LATIN CAPITAL LETTER I WITH MACRON]
+ case L'\u012C': // [LATIN CAPITAL LETTER I WITH BREVE]
+ case L'\u012E': // [LATIN CAPITAL LETTER I WITH OGONEK]
+ case L'\u0130': // [LATIN CAPITAL LETTER I WITH DOT ABOVE]
+ case L'\u0196': // [LATIN CAPITAL LETTER IOTA]
+ case L'\u0197': // [LATIN CAPITAL LETTER I WITH STROKE]
+ case L'\u01CF': // [LATIN CAPITAL LETTER I WITH CARON]
+ case L'\u0208': // [LATIN CAPITAL LETTER I WITH DOUBLE GRAVE]
+ case L'\u020A': // [LATIN CAPITAL LETTER I WITH INVERTED BREVE]
+ case L'\u026A': // [LATIN LETTER SMALL CAPITAL I]
+ case L'\u1D7B': // [LATIN SMALL CAPITAL LETTER I WITH STROKE]
+ case L'\u1E2C': // [LATIN CAPITAL LETTER I WITH TILDE BELOW]
+ case L'\u1E2E': // [LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE]
+ case L'\u1EC8': // [LATIN CAPITAL LETTER I WITH HOOK ABOVE]
+ case L'\u1ECA': // [LATIN CAPITAL LETTER I WITH DOT BELOW]
+ case L'\u24BE': // [CIRCLED LATIN CAPITAL LETTER I]
+ case L'\uA7FE': // [LATIN EPIGRAPHIC LETTER I LONGA]
+ case L'\uFF29': // [FULLWIDTH LATIN CAPITAL LETTER I]
+ *out = 'I';
+ break;
+ case L'\u00EC': // [LATIN SMALL LETTER I WITH GRAVE]
+ case L'\u00ED': // [LATIN SMALL LETTER I WITH ACUTE]
+ case L'\u00EE': // [LATIN SMALL LETTER I WITH CIRCUMFLEX]
+ case L'\u00EF': // [LATIN SMALL LETTER I WITH DIAERESIS]
+ case L'\u0129': // [LATIN SMALL LETTER I WITH TILDE]
+ case L'\u012B': // [LATIN SMALL LETTER I WITH MACRON]
+ case L'\u012D': // [LATIN SMALL LETTER I WITH BREVE]
+ case L'\u012F': // [LATIN SMALL LETTER I WITH OGONEK]
+ case L'\u0131': // [LATIN SMALL LETTER DOTLESS I]
+ case L'\u01D0': // [LATIN SMALL LETTER I WITH CARON]
+ case L'\u0209': // [LATIN SMALL LETTER I WITH DOUBLE GRAVE]
+ case L'\u020B': // [LATIN SMALL LETTER I WITH INVERTED BREVE]
+ case L'\u0268': // [LATIN SMALL LETTER I WITH STROKE]
+ case L'\u1D09': // [LATIN SMALL LETTER TURNED I]
+ case L'\u1D62': // [LATIN SUBSCRIPT SMALL LETTER I]
+ case L'\u1D7C': // [LATIN SMALL LETTER IOTA WITH STROKE]
+ case L'\u1D96': // [LATIN SMALL LETTER I WITH RETROFLEX HOOK]
+ case L'\u1E2D': // [LATIN SMALL LETTER I WITH TILDE BELOW]
+ case L'\u1E2F': // [LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE]
+ case L'\u1EC9': // [LATIN SMALL LETTER I WITH HOOK ABOVE]
+ case L'\u1ECB': // [LATIN SMALL LETTER I WITH DOT BELOW]
+ case L'\u2071': // [SUPERSCRIPT LATIN SMALL LETTER I]
+ case L'\u24D8': // [CIRCLED LATIN SMALL LETTER I]
+ case L'\uFF49': // [FULLWIDTH LATIN SMALL LETTER I]
+ *out = 'i';
+ break;
+ case L'\u0132': // [LATIN CAPITAL LIGATURE IJ]
+ *out = 'I';
+ *out = 'J';
+ break;
+ case L'\u24A4': // [PARENTHESIZED LATIN SMALL LETTER I]
+ *out = '(';
+ *out = 'i';
+ *out = ')';
+ break;
+ case L'\u0133': // [LATIN SMALL LIGATURE IJ]
+ *out = 'i';
+ *out = 'j';
+ break;
+ case L'\u0134': // [LATIN CAPITAL LETTER J WITH CIRCUMFLEX]
+ case L'\u0248': // [LATIN CAPITAL LETTER J WITH STROKE]
+ case L'\u1D0A': // [LATIN LETTER SMALL CAPITAL J]
+ case L'\u24BF': // [CIRCLED LATIN CAPITAL LETTER J]
+ case L'\uFF2A': // [FULLWIDTH LATIN CAPITAL LETTER J]
+ *out = 'J';
+ break;
+ case L'\u0135': // [LATIN SMALL LETTER J WITH CIRCUMFLEX]
+ case L'\u01F0': // [LATIN SMALL LETTER J WITH CARON]
+ case L'\u0237': // [LATIN SMALL LETTER DOTLESS J]
+ case L'\u0249': // [LATIN SMALL LETTER J WITH STROKE]
+ case L'\u025F': // [LATIN SMALL LETTER DOTLESS J WITH STROKE]
+ case L'\u0284': // [LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK]
+ case L'\u029D': // [LATIN SMALL LETTER J WITH CROSSED-TAIL]
+ case L'\u24D9': // [CIRCLED LATIN SMALL LETTER J]
+ case L'\u2C7C': // [LATIN SUBSCRIPT SMALL LETTER J]
+ case L'\uFF4A': // [FULLWIDTH LATIN SMALL LETTER J]
+ *out = 'j';
+ break;
+ case L'\u24A5': // [PARENTHESIZED LATIN SMALL LETTER J]
+ *out = '(';
+ *out = 'j';
+ *out = ')';
+ break;
+ case L'\u0136': // [LATIN CAPITAL LETTER K WITH CEDILLA]
+ case L'\u0198': // [LATIN CAPITAL LETTER K WITH HOOK]
+ case L'\u01E8': // [LATIN CAPITAL LETTER K WITH CARON]
+ case L'\u1D0B': // [LATIN LETTER SMALL CAPITAL K]
+ case L'\u1E30': // [LATIN CAPITAL LETTER K WITH ACUTE]
+ case L'\u1E32': // [LATIN CAPITAL LETTER K WITH DOT BELOW]
+ case L'\u1E34': // [LATIN CAPITAL LETTER K WITH LINE BELOW]
+ case L'\u24C0': // [CIRCLED LATIN CAPITAL LETTER K]
+ case L'\u2C69': // [LATIN CAPITAL LETTER K WITH DESCENDER]
+ case L'\uA740': // [LATIN CAPITAL LETTER K WITH STROKE]
+ case L'\uA742': // [LATIN CAPITAL LETTER K WITH DIAGONAL STROKE]
+ case L'\uA744': // [LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE]
+ case L'\uFF2B': // [FULLWIDTH LATIN CAPITAL LETTER K]
+ *out = 'K';
+ break;
+ case L'\u0137': // [LATIN SMALL LETTER K WITH CEDILLA]
+ case L'\u0199': // [LATIN SMALL LETTER K WITH HOOK]
+ case L'\u01E9': // [LATIN SMALL LETTER K WITH CARON]
+ case L'\u029E': // [LATIN SMALL LETTER TURNED K]
+ case L'\u1D84': // [LATIN SMALL LETTER K WITH PALATAL HOOK]
+ case L'\u1E31': // [LATIN SMALL LETTER K WITH ACUTE]
+ case L'\u1E33': // [LATIN SMALL LETTER K WITH DOT BELOW]
+ case L'\u1E35': // [LATIN SMALL LETTER K WITH LINE BELOW]
+ case L'\u24DA': // [CIRCLED LATIN SMALL LETTER K]
+ case L'\u2C6A': // [LATIN SMALL LETTER K WITH DESCENDER]
+ case L'\uA741': // [LATIN SMALL LETTER K WITH STROKE]
+ case L'\uA743': // [LATIN SMALL LETTER K WITH DIAGONAL STROKE]
+ case L'\uA745': // [LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE]
+ case L'\uFF4B': // [FULLWIDTH LATIN SMALL LETTER K]
+ *out = 'k';
+ break;
+ case L'\u24A6': // [PARENTHESIZED LATIN SMALL LETTER K]
+ *out = '(';
+ *out = 'k';
+ *out = ')';
+ break;
+ case L'\u0139': // [LATIN CAPITAL LETTER L WITH ACUTE]
+ case L'\u013B': // [LATIN CAPITAL LETTER L WITH CEDILLA]
+ case L'\u013D': // [LATIN CAPITAL LETTER L WITH CARON]
+ case L'\u013F': // [LATIN CAPITAL LETTER L WITH MIDDLE DOT]
+ case L'\u0141': // [LATIN CAPITAL LETTER L WITH STROKE]
+ case L'\u023D': // [LATIN CAPITAL LETTER L WITH BAR]
+ case L'\u029F': // [LATIN LETTER SMALL CAPITAL L]
+ case L'\u1D0C': // [LATIN LETTER SMALL CAPITAL L WITH STROKE]
+ case L'\u1E36': // [LATIN CAPITAL LETTER L WITH DOT BELOW]
+ case L'\u1E38': // [LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON]
+ case L'\u1E3A': // [LATIN CAPITAL LETTER L WITH LINE BELOW]
+ case L'\u1E3C': // [LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW]
+ case L'\u24C1': // [CIRCLED LATIN CAPITAL LETTER L]
+ case L'\u2C60': // [LATIN CAPITAL LETTER L WITH DOUBLE BAR]
+ case L'\u2C62': // [LATIN CAPITAL LETTER L WITH MIDDLE TILDE]
+ case L'\uA746': // [LATIN CAPITAL LETTER BROKEN L]
+ case L'\uA748': // [LATIN CAPITAL LETTER L WITH HIGH STROKE]
+ case L'\uA780': // [LATIN CAPITAL LETTER TURNED L]
+ case L'\uFF2C': // [FULLWIDTH LATIN CAPITAL LETTER L]
+ *out = 'L';
+ break;
+ case L'\u013A': // [LATIN SMALL LETTER L WITH ACUTE]
+ case L'\u013C': // [LATIN SMALL LETTER L WITH CEDILLA]
+ case L'\u013E': // [LATIN SMALL LETTER L WITH CARON]
+ case L'\u0140': // [LATIN SMALL LETTER L WITH MIDDLE DOT]
+ case L'\u0142': // [LATIN SMALL LETTER L WITH STROKE]
+ case L'\u019A': // [LATIN SMALL LETTER L WITH BAR]
+ case L'\u0234': // [LATIN SMALL LETTER L WITH CURL]
+ case L'\u026B': // [LATIN SMALL LETTER L WITH MIDDLE TILDE]
+ case L'\u026C': // [LATIN SMALL LETTER L WITH BELT]
+ case L'\u026D': // [LATIN SMALL LETTER L WITH RETROFLEX HOOK]
+ case L'\u1D85': // [LATIN SMALL LETTER L WITH PALATAL HOOK]
+ case L'\u1E37': // [LATIN SMALL LETTER L WITH DOT BELOW]
+ case L'\u1E39': // [LATIN SMALL LETTER L WITH DOT BELOW AND MACRON]
+ case L'\u1E3B': // [LATIN SMALL LETTER L WITH LINE BELOW]
+ case L'\u1E3D': // [LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW]
+ case L'\u24DB': // [CIRCLED LATIN SMALL LETTER L]
+ case L'\u2C61': // [LATIN SMALL LETTER L WITH DOUBLE BAR]
+ case L'\uA747': // [LATIN SMALL LETTER BROKEN L]
+ case L'\uA749': // [LATIN SMALL LETTER L WITH HIGH STROKE]
+ case L'\uA781': // [LATIN SMALL LETTER TURNED L]
+ case L'\uFF4C': // [FULLWIDTH LATIN SMALL LETTER L]
+ *out = 'l';
+ break;
+ case L'\u01C7': // [LATIN CAPITAL LETTER LJ]
+ *out = 'L';
+ *out = 'J';
+ break;
+ case L'\u1EFA': // [LATIN CAPITAL LETTER MIDDLE-WELSH LL]
+ *out = 'L';
+ *out = 'L';
+ break;
+ case L'\u01C8': // [LATIN CAPITAL LETTER L WITH SMALL LETTER J]
+ *out = 'L';
+ *out = 'j';
+ break;
+ case L'\u24A7': // [PARENTHESIZED LATIN SMALL LETTER L]
+ *out = '(';
+ *out = 'l';
+ *out = ')';
+ break;
+ case L'\u01C9': // [LATIN SMALL LETTER LJ]
+ *out = 'l';
+ *out = 'j';
+ break;
+ case L'\u1EFB': // [LATIN SMALL LETTER MIDDLE-WELSH LL]
+ *out = 'l';
+ *out = 'l';
+ break;
+ case L'\u02AA': // [LATIN SMALL LETTER LS DIGRAPH]
+ *out = 'l';
+ *out = 's';
+ break;
+ case L'\u02AB': // [LATIN SMALL LETTER LZ DIGRAPH]
+ *out = 'l';
+ *out = 'z';
+ break;
+ case L'\u019C': // [LATIN CAPITAL LETTER TURNED M]
+ case L'\u1D0D': // [LATIN LETTER SMALL CAPITAL M]
+ case L'\u1E3E': // [LATIN CAPITAL LETTER M WITH ACUTE]
+ case L'\u1E40': // [LATIN CAPITAL LETTER M WITH DOT ABOVE]
+ case L'\u1E42': // [LATIN CAPITAL LETTER M WITH DOT BELOW]
+ case L'\u24C2': // [CIRCLED LATIN CAPITAL LETTER M]
+ case L'\u2C6E': // [LATIN CAPITAL LETTER M WITH HOOK]
+ case L'\uA7FD': // [LATIN EPIGRAPHIC LETTER INVERTED M]
+ case L'\uA7FF': // [LATIN EPIGRAPHIC LETTER ARCHAIC M]
+ case L'\uFF2D': // [FULLWIDTH LATIN CAPITAL LETTER M]
+ *out = 'M';
+ break;
+ case L'\u026F': // [LATIN SMALL LETTER TURNED M]
+ case L'\u0270': // [LATIN SMALL LETTER TURNED M WITH LONG LEG]
+ case L'\u0271': // [LATIN SMALL LETTER M WITH HOOK]
+ case L'\u1D6F': // [LATIN SMALL LETTER M WITH MIDDLE TILDE]
+ case L'\u1D86': // [LATIN SMALL LETTER M WITH PALATAL HOOK]
+ case L'\u1E3F': // [LATIN SMALL LETTER M WITH ACUTE]
+ case L'\u1E41': // [LATIN SMALL LETTER M WITH DOT ABOVE]
+ case L'\u1E43': // [LATIN SMALL LETTER M WITH DOT BELOW]
+ case L'\u24DC': // [CIRCLED LATIN SMALL LETTER M]
+ case L'\uFF4D': // [FULLWIDTH LATIN SMALL LETTER M]
+ *out = 'm';
+ break;
+ case L'\u24A8': // [PARENTHESIZED LATIN SMALL LETTER M]
+ *out = '(';
+ *out = 'm';
+ *out = ')';
+ break;
+ case L'\u00D1': // [LATIN CAPITAL LETTER N WITH TILDE]
+ case L'\u0143': // [LATIN CAPITAL LETTER N WITH ACUTE]
+ case L'\u0145': // [LATIN CAPITAL LETTER N WITH CEDILLA]
+ case L'\u0147': // [LATIN CAPITAL LETTER N WITH CARON]
+ case L'\u014A': // [LATIN CAPITAL LETTER ENG]
+ case L'\u019D': // [LATIN CAPITAL LETTER N WITH LEFT HOOK]
+ case L'\u01F8': // [LATIN CAPITAL LETTER N WITH GRAVE]
+ case L'\u0220': // [LATIN CAPITAL LETTER N WITH LONG RIGHT LEG]
+ case L'\u0274': // [LATIN LETTER SMALL CAPITAL N]
+ case L'\u1D0E': // [LATIN LETTER SMALL CAPITAL REVERSED N]
+ case L'\u1E44': // [LATIN CAPITAL LETTER N WITH DOT ABOVE]
+ case L'\u1E46': // [LATIN CAPITAL LETTER N WITH DOT BELOW]
+ case L'\u1E48': // [LATIN CAPITAL LETTER N WITH LINE BELOW]
+ case L'\u1E4A': // [LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW]
+ case L'\u24C3': // [CIRCLED LATIN CAPITAL LETTER N]
+ case L'\uFF2E': // [FULLWIDTH LATIN CAPITAL LETTER N]
+ *out = 'N';
+ break;
+ case L'\u00F1': // [LATIN SMALL LETTER N WITH TILDE]
+ case L'\u0144': // [LATIN SMALL LETTER N WITH ACUTE]
+ case L'\u0146': // [LATIN SMALL LETTER N WITH CEDILLA]
+ case L'\u0148': // [LATIN SMALL LETTER N WITH CARON]
+ case L'\u0149': // [LATIN SMALL LETTER N PRECEDED BY APOSTROPHE]
+ case L'\u014B': // [LATIN SMALL LETTER ENG]
+ case L'\u019E': // [LATIN SMALL LETTER N WITH LONG RIGHT LEG]
+ case L'\u01F9': // [LATIN SMALL LETTER N WITH GRAVE]
+ case L'\u0235': // [LATIN SMALL LETTER N WITH CURL]
+ case L'\u0272': // [LATIN SMALL LETTER N WITH LEFT HOOK]
+ case L'\u0273': // [LATIN SMALL LETTER N WITH RETROFLEX HOOK]
+ case L'\u1D70': // [LATIN SMALL LETTER N WITH MIDDLE TILDE]
+ case L'\u1D87': // [LATIN SMALL LETTER N WITH PALATAL HOOK]
+ case L'\u1E45': // [LATIN SMALL LETTER N WITH DOT ABOVE]
+ case L'\u1E47': // [LATIN SMALL LETTER N WITH DOT BELOW]
+ case L'\u1E49': // [LATIN SMALL LETTER N WITH LINE BELOW]
+ case L'\u1E4B': // [LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW]
+ case L'\u207F': // [SUPERSCRIPT LATIN SMALL LETTER N]
+ case L'\u24DD': // [CIRCLED LATIN SMALL LETTER N]
+ case L'\uFF4E': // [FULLWIDTH LATIN SMALL LETTER N]
+ *out = 'n';
+ break;
+ case L'\u01CA': // [LATIN CAPITAL LETTER NJ]
+ *out = 'N';
+ *out = 'J';
+ break;
+ case L'\u01CB': // [LATIN CAPITAL LETTER N WITH SMALL LETTER J]
+ *out = 'N';
+ *out = 'j';
+ break;
+ case L'\u24A9': // [PARENTHESIZED LATIN SMALL LETTER N]
+ *out = '(';
+ *out = 'n';
+ *out = ')';
+ break;
+ case L'\u01CC': // [LATIN SMALL LETTER NJ]
+ *out = 'n';
+ *out = 'j';
+ break;
+ case L'\u00D2': // [LATIN CAPITAL LETTER O WITH GRAVE]
+ case L'\u00D3': // [LATIN CAPITAL LETTER O WITH ACUTE]
+ case L'\u00D4': // [LATIN CAPITAL LETTER O WITH CIRCUMFLEX]
+ case L'\u00D5': // [LATIN CAPITAL LETTER O WITH TILDE]
+ case L'\u00D6': // [LATIN CAPITAL LETTER O WITH DIAERESIS]
+ case L'\u00D8': // [LATIN CAPITAL LETTER O WITH STROKE]
+ case L'\u014C': // [LATIN CAPITAL LETTER O WITH MACRON]
+ case L'\u014E': // [LATIN CAPITAL LETTER O WITH BREVE]
+ case L'\u0150': // [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE]
+ case L'\u0186': // [LATIN CAPITAL LETTER OPEN O]
+ case L'\u019F': // [LATIN CAPITAL LETTER O WITH MIDDLE TILDE]
+ case L'\u01A0': // [LATIN CAPITAL LETTER O WITH HORN]
+ case L'\u01D1': // [LATIN CAPITAL LETTER O WITH CARON]
+ case L'\u01EA': // [LATIN CAPITAL LETTER O WITH OGONEK]
+ case L'\u01EC': // [LATIN CAPITAL LETTER O WITH OGONEK AND MACRON]
+ case L'\u01FE': // [LATIN CAPITAL LETTER O WITH STROKE AND ACUTE]
+ case L'\u020C': // [LATIN CAPITAL LETTER O WITH DOUBLE GRAVE]
+ case L'\u020E': // [LATIN CAPITAL LETTER O WITH INVERTED BREVE]
+ case L'\u022A': // [LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON]
+ case L'\u022C': // [LATIN CAPITAL LETTER O WITH TILDE AND MACRON]
+ case L'\u022E': // [LATIN CAPITAL LETTER O WITH DOT ABOVE]
+ case L'\u0230': // [LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON]
+ case L'\u1D0F': // [LATIN LETTER SMALL CAPITAL O]
+ case L'\u1D10': // [LATIN LETTER SMALL CAPITAL OPEN O]
+ case L'\u1E4C': // [LATIN CAPITAL LETTER O WITH TILDE AND ACUTE]
+ case L'\u1E4E': // [LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS]
+ case L'\u1E50': // [LATIN CAPITAL LETTER O WITH MACRON AND GRAVE]
+ case L'\u1E52': // [LATIN CAPITAL LETTER O WITH MACRON AND ACUTE]
+ case L'\u1ECC': // [LATIN CAPITAL LETTER O WITH DOT BELOW]
+ case L'\u1ECE': // [LATIN CAPITAL LETTER O WITH HOOK ABOVE]
+ case L'\u1ED0': // [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE]
+ case L'\u1ED2': // [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE]
+ case L'\u1ED4': // [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE]
+ case L'\u1ED6': // [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE]
+ case L'\u1ED8': // [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW]
+ case L'\u1EDA': // [LATIN CAPITAL LETTER O WITH HORN AND ACUTE]
+ case L'\u1EDC': // [LATIN CAPITAL LETTER O WITH HORN AND GRAVE]
+ case L'\u1EDE': // [LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE]
+ case L'\u1EE0': // [LATIN CAPITAL LETTER O WITH HORN AND TILDE]
+ case L'\u1EE2': // [LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW]
+ case L'\u24C4': // [CIRCLED LATIN CAPITAL LETTER O]
+ case L'\uA74A': // [LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY]
+ case L'\uA74C': // [LATIN CAPITAL LETTER O WITH LOOP]
+ case L'\uFF2F': // [FULLWIDTH LATIN CAPITAL LETTER O]
+ *out = 'O';
+ break;
+ case L'\u00F2': // [LATIN SMALL LETTER O WITH GRAVE]
+ case L'\u00F3': // [LATIN SMALL LETTER O WITH ACUTE]
+ case L'\u00F4': // [LATIN SMALL LETTER O WITH CIRCUMFLEX]
+ case L'\u00F5': // [LATIN SMALL LETTER O WITH TILDE]
+ case L'\u00F6': // [LATIN SMALL LETTER O WITH DIAERESIS]
+ case L'\u00F8': // [LATIN SMALL LETTER O WITH STROKE]
+ case L'\u014D': // [LATIN SMALL LETTER O WITH MACRON]
+ case L'\u014F': // [LATIN SMALL LETTER O WITH BREVE]
+ case L'\u0151': // [LATIN SMALL LETTER O WITH DOUBLE ACUTE]
+ case L'\u01A1': // [LATIN SMALL LETTER O WITH HORN]
+ case L'\u01D2': // [LATIN SMALL LETTER O WITH CARON]
+ case L'\u01EB': // [LATIN SMALL LETTER O WITH OGONEK]
+ case L'\u01ED': // [LATIN SMALL LETTER O WITH OGONEK AND MACRON]
+ case L'\u01FF': // [LATIN SMALL LETTER O WITH STROKE AND ACUTE]
+ case L'\u020D': // [LATIN SMALL LETTER O WITH DOUBLE GRAVE]
+ case L'\u020F': // [LATIN SMALL LETTER O WITH INVERTED BREVE]
+ case L'\u022B': // [LATIN SMALL LETTER O WITH DIAERESIS AND MACRON]
+ case L'\u022D': // [LATIN SMALL LETTER O WITH TILDE AND MACRON]
+ case L'\u022F': // [LATIN SMALL LETTER O WITH DOT ABOVE]
+ case L'\u0231': // [LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON]
+ case L'\u0254': // [LATIN SMALL LETTER OPEN O]
+ case L'\u0275': // [LATIN SMALL LETTER BARRED O]
+ case L'\u1D16': // [LATIN SMALL LETTER TOP HALF O]
+ case L'\u1D17': // [LATIN SMALL LETTER BOTTOM HALF O]
+ case L'\u1D97': // [LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK]
+ case L'\u1E4D': // [LATIN SMALL LETTER O WITH TILDE AND ACUTE]
+ case L'\u1E4F': // [LATIN SMALL LETTER O WITH TILDE AND DIAERESIS]
+ case L'\u1E51': // [LATIN SMALL LETTER O WITH MACRON AND GRAVE]
+ case L'\u1E53': // [LATIN SMALL LETTER O WITH MACRON AND ACUTE]
+ case L'\u1ECD': // [LATIN SMALL LETTER O WITH DOT BELOW]
+ case L'\u1ECF': // [LATIN SMALL LETTER O WITH HOOK ABOVE]
+ case L'\u1ED1': // [LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE]
+ case L'\u1ED3': // [LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE]
+ case L'\u1ED5': // [LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE]
+ case L'\u1ED7': // [LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE]
+ case L'\u1ED9': // [LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW]
+ case L'\u1EDB': // [LATIN SMALL LETTER O WITH HORN AND ACUTE]
+ case L'\u1EDD': // [LATIN SMALL LETTER O WITH HORN AND GRAVE]
+ case L'\u1EDF': // [LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE]
+ case L'\u1EE1': // [LATIN SMALL LETTER O WITH HORN AND TILDE]
+ case L'\u1EE3': // [LATIN SMALL LETTER O WITH HORN AND DOT BELOW]
+ case L'\u2092': // [LATIN SUBSCRIPT SMALL LETTER O]
+ case L'\u24DE': // [CIRCLED LATIN SMALL LETTER O]
+ case L'\u2C7A': // [LATIN SMALL LETTER O WITH LOW RING INSIDE]
+ case L'\uA74B': // [LATIN SMALL LETTER O WITH LONG STROKE OVERLAY]
+ case L'\uA74D': // [LATIN SMALL LETTER O WITH LOOP]
+ case L'\uFF4F': // [FULLWIDTH LATIN SMALL LETTER O]
+ *out = 'o';
+ break;
+ case L'\u0152': // [LATIN CAPITAL LIGATURE OE]
+ case L'\u0276': // [LATIN LETTER SMALL CAPITAL OE]
+ *out = 'O';
+ *out = 'E';
+ break;
+ case L'\uA74E': // [LATIN CAPITAL LETTER OO]
+ *out = 'O';
+ *out = 'O';
+ break;
+ case L'\u0222': // [LATIN CAPITAL LETTER OU]
+ case L'\u1D15': // [LATIN LETTER SMALL CAPITAL OU]
+ *out = 'O';
+ *out = 'U';
+ break;
+ case L'\u24AA': // [PARENTHESIZED LATIN SMALL LETTER O]
+ *out = '(';
+ *out = 'o';
+ *out = ')';
+ break;
+ case L'\u0153': // [LATIN SMALL LIGATURE OE]
+ case L'\u1D14': // [LATIN SMALL LETTER TURNED OE]
+ *out = 'o';
+ *out = 'e';
+ break;
+ case L'\uA74F': // [LATIN SMALL LETTER OO]
+ *out = 'o';
+ *out = 'o';
+ break;
+ case L'\u0223': // [LATIN SMALL LETTER OU]
+ *out = 'o';
+ *out = 'u';
+ break;
+ case L'\u01A4': // [LATIN CAPITAL LETTER P WITH HOOK]
+ case L'\u1D18': // [LATIN LETTER SMALL CAPITAL P]
+ case L'\u1E54': // [LATIN CAPITAL LETTER P WITH ACUTE]
+ case L'\u1E56': // [LATIN CAPITAL LETTER P WITH DOT ABOVE]
+ case L'\u24C5': // [CIRCLED LATIN CAPITAL LETTER P]
+ case L'\u2C63': // [LATIN CAPITAL LETTER P WITH STROKE]
+ case L'\uA750': // [LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER]
+ case L'\uA752': // [LATIN CAPITAL LETTER P WITH FLOURISH]
+ case L'\uA754': // [LATIN CAPITAL LETTER P WITH SQUIRREL TAIL]
+ case L'\uFF30': // [FULLWIDTH LATIN CAPITAL LETTER P]
+ *out = 'P';
+ break;
+ case L'\u01A5': // [LATIN SMALL LETTER P WITH HOOK]
+ case L'\u1D71': // [LATIN SMALL LETTER P WITH MIDDLE TILDE]
+ case L'\u1D7D': // [LATIN SMALL LETTER P WITH STROKE]
+ case L'\u1D88': // [LATIN SMALL LETTER P WITH PALATAL HOOK]
+ case L'\u1E55': // [LATIN SMALL LETTER P WITH ACUTE]
+ case L'\u1E57': // [LATIN SMALL LETTER P WITH DOT ABOVE]
+ case L'\u24DF': // [CIRCLED LATIN SMALL LETTER P]
+ case L'\uA751': // [LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER]
+ case L'\uA753': // [LATIN SMALL LETTER P WITH FLOURISH]
+ case L'\uA755': // [LATIN SMALL LETTER P WITH SQUIRREL TAIL]
+ case L'\uA7FC': // [LATIN EPIGRAPHIC LETTER REVERSED P]
+ case L'\uFF50': // [FULLWIDTH LATIN SMALL LETTER P]
+ *out = 'p';
+ break;
+ case L'\u24AB': // [PARENTHESIZED LATIN SMALL LETTER P]
+ *out = '(';
+ *out = 'p';
+ *out = ')';
+ break;
+ case L'\u024A': // [LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL]
+ case L'\u24C6': // [CIRCLED LATIN CAPITAL LETTER Q]
+ case L'\uA756': // [LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER]
+ case L'\uA758': // [LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE]
+ case L'\uFF31': // [FULLWIDTH LATIN CAPITAL LETTER Q]
+ *out = 'Q';
+ break;
+ case L'\u0138': // [LATIN SMALL LETTER KRA]
+ case L'\u024B': // [LATIN SMALL LETTER Q WITH HOOK TAIL]
+ case L'\u02A0': // [LATIN SMALL LETTER Q WITH HOOK]
+ case L'\u24E0': // [CIRCLED LATIN SMALL LETTER Q]
+ case L'\uA757': // [LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER]
+ case L'\uA759': // [LATIN SMALL LETTER Q WITH DIAGONAL STROKE]
+ case L'\uFF51': // [FULLWIDTH LATIN SMALL LETTER Q]
+ *out = 'q';
+ break;
+ case L'\u24AC': // [PARENTHESIZED LATIN SMALL LETTER Q]
+ *out = '(';
+ *out = 'q';
+ *out = ')';
+ break;
+ case L'\u0239': // [LATIN SMALL LETTER QP DIGRAPH]
+ *out = 'q';
+ *out = 'p';
+ break;
+ case L'\u0154': // [LATIN CAPITAL LETTER R WITH ACUTE]
+ case L'\u0156': // [LATIN CAPITAL LETTER R WITH CEDILLA]
+ case L'\u0158': // [LATIN CAPITAL LETTER R WITH CARON]
+ case L'\u0210': // [LATIN CAPITAL LETTER R WITH DOUBLE GRAVE]
+ case L'\u0212': // [LATIN CAPITAL LETTER R WITH INVERTED BREVE]
+ case L'\u024C': // [LATIN CAPITAL LETTER R WITH STROKE]
+ case L'\u0280': // [LATIN LETTER SMALL CAPITAL R]
+ case L'\u0281': // [LATIN LETTER SMALL CAPITAL INVERTED R]
+ case L'\u1D19': // [LATIN LETTER SMALL CAPITAL REVERSED R]
+ case L'\u1D1A': // [LATIN LETTER SMALL CAPITAL TURNED R]
+ case L'\u1E58': // [LATIN CAPITAL LETTER R WITH DOT ABOVE]
+ case L'\u1E5A': // [LATIN CAPITAL LETTER R WITH DOT BELOW]
+ case L'\u1E5C': // [LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON]
+ case L'\u1E5E': // [LATIN CAPITAL LETTER R WITH LINE BELOW]
+ case L'\u24C7': // [CIRCLED LATIN CAPITAL LETTER R]
+ case L'\u2C64': // [LATIN CAPITAL LETTER R WITH TAIL]
+ case L'\uA75A': // [LATIN CAPITAL LETTER R ROTUNDA]
+ case L'\uA782': // [LATIN CAPITAL LETTER INSULAR R]
+ case L'\uFF32': // [FULLWIDTH LATIN CAPITAL LETTER R]
+ *out = 'R';
+ break;
+ case L'\u0155': // [LATIN SMALL LETTER R WITH ACUTE]
+ case L'\u0157': // [LATIN SMALL LETTER R WITH CEDILLA]
+ case L'\u0159': // [LATIN SMALL LETTER R WITH CARON]
+ case L'\u0211': // [LATIN SMALL LETTER R WITH DOUBLE GRAVE]
+ case L'\u0213': // [LATIN SMALL LETTER R WITH INVERTED BREVE]
+ case L'\u024D': // [LATIN SMALL LETTER R WITH STROKE]
+ case L'\u027C': // [LATIN SMALL LETTER R WITH LONG LEG]
+ case L'\u027D': // [LATIN SMALL LETTER R WITH TAIL]
+ case L'\u027E': // [LATIN SMALL LETTER R WITH FISHHOOK]
+ case L'\u027F': // [LATIN SMALL LETTER REVERSED R WITH FISHHOOK]
+ case L'\u1D63': // [LATIN SUBSCRIPT SMALL LETTER R]
+ case L'\u1D72': // [LATIN SMALL LETTER R WITH MIDDLE TILDE]
+ case L'\u1D73': // [LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE]
+ case L'\u1D89': // [LATIN SMALL LETTER R WITH PALATAL HOOK]
+ case L'\u1E59': // [LATIN SMALL LETTER R WITH DOT ABOVE]
+ case L'\u1E5B': // [LATIN SMALL LETTER R WITH DOT BELOW]
+ case L'\u1E5D': // [LATIN SMALL LETTER R WITH DOT BELOW AND MACRON]
+ case L'\u1E5F': // [LATIN SMALL LETTER R WITH LINE BELOW]
+ case L'\u24E1': // [CIRCLED LATIN SMALL LETTER R]
+ case L'\uA75B': // [LATIN SMALL LETTER R ROTUNDA]
+ case L'\uA783': // [LATIN SMALL LETTER INSULAR R]
+ case L'\uFF52': // [FULLWIDTH LATIN SMALL LETTER R]
+ *out = 'r';
+ break;
+ case L'\u24AD': // [PARENTHESIZED LATIN SMALL LETTER R]
+ *out = '(';
+ *out = 'r';
+ *out = ')';
+ break;
+ case L'\u015A': // [LATIN CAPITAL LETTER S WITH ACUTE]
+ case L'\u015C': // [LATIN CAPITAL LETTER S WITH CIRCUMFLEX]
+ case L'\u015E': // [LATIN CAPITAL LETTER S WITH CEDILLA]
+ case L'\u0160': // [LATIN CAPITAL LETTER S WITH CARON]
+ case L'\u0218': // [LATIN CAPITAL LETTER S WITH COMMA BELOW]
+ case L'\u1E60': // [LATIN CAPITAL LETTER S WITH DOT ABOVE]
+ case L'\u1E62': // [LATIN CAPITAL LETTER S WITH DOT BELOW]
+ case L'\u1E64': // [LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE]
+ case L'\u1E66': // [LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE]
+ case L'\u1E68': // [LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE]
+ case L'\u24C8': // [CIRCLED LATIN CAPITAL LETTER S]
+ case L'\uA731': // [LATIN LETTER SMALL CAPITAL S]
+ case L'\uA785': // [LATIN SMALL LETTER INSULAR S]
+ case L'\uFF33': // [FULLWIDTH LATIN CAPITAL LETTER S]
+ *out = 'S';
+ break;
+ case L'\u015B': // [LATIN SMALL LETTER S WITH ACUTE]
+ case L'\u015D': // [LATIN SMALL LETTER S WITH CIRCUMFLEX]
+ case L'\u015F': // [LATIN SMALL LETTER S WITH CEDILLA]
+ case L'\u0161': // [LATIN SMALL LETTER S WITH CARON]
+ case L'\u017F': // [LATIN SMALL LETTER LONG S]
+ case L'\u0219': // [LATIN SMALL LETTER S WITH COMMA BELOW]
+ case L'\u023F': // [LATIN SMALL LETTER S WITH SWASH TAIL]
+ case L'\u0282': // [LATIN SMALL LETTER S WITH HOOK]
+ case L'\u1D74': // [LATIN SMALL LETTER S WITH MIDDLE TILDE]
+ case L'\u1D8A': // [LATIN SMALL LETTER S WITH PALATAL HOOK]
+ case L'\u1E61': // [LATIN SMALL LETTER S WITH DOT ABOVE]
+ case L'\u1E63': // [LATIN SMALL LETTER S WITH DOT BELOW]
+ case L'\u1E65': // [LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE]
+ case L'\u1E67': // [LATIN SMALL LETTER S WITH CARON AND DOT ABOVE]
+ case L'\u1E69': // [LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE]
+ case L'\u1E9C': // [LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE]
+ case L'\u1E9D': // [LATIN SMALL LETTER LONG S WITH HIGH STROKE]
+ case L'\u24E2': // [CIRCLED LATIN SMALL LETTER S]
+ case L'\uA784': // [LATIN CAPITAL LETTER INSULAR S]
+ case L'\uFF53': // [FULLWIDTH LATIN SMALL LETTER S]
+ *out = 's';
+ break;
+ case L'\u1E9E': // [LATIN CAPITAL LETTER SHARP S]
+ *out = 'S';
+ *out = 'S';
+ break;
+ case L'\u24AE': // [PARENTHESIZED LATIN SMALL LETTER S]
+ *out = '(';
+ *out = 's';
+ *out = ')';
+ break;
+ case L'\u00DF': // [LATIN SMALL LETTER SHARP S]
+ *out = 's';
+ *out = 's';
+ break;
+ case L'\uFB06': // [LATIN SMALL LIGATURE ST]
+ *out = 's';
+ *out = 't';
+ break;
+ case L'\u0162': // [LATIN CAPITAL LETTER T WITH CEDILLA]
+ case L'\u0164': // [LATIN CAPITAL LETTER T WITH CARON]
+ case L'\u0166': // [LATIN CAPITAL LETTER T WITH STROKE]
+ case L'\u01AC': // [LATIN CAPITAL LETTER T WITH HOOK]
+ case L'\u01AE': // [LATIN CAPITAL LETTER T WITH RETROFLEX HOOK]
+ case L'\u021A': // [LATIN CAPITAL LETTER T WITH COMMA BELOW]
+ case L'\u023E': // [LATIN CAPITAL LETTER T WITH DIAGONAL STROKE]
+ case L'\u1D1B': // [LATIN LETTER SMALL CAPITAL T]
+ case L'\u1E6A': // [LATIN CAPITAL LETTER T WITH DOT ABOVE]
+ case L'\u1E6C': // [LATIN CAPITAL LETTER T WITH DOT BELOW]
+ case L'\u1E6E': // [LATIN CAPITAL LETTER T WITH LINE BELOW]
+ case L'\u1E70': // [LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW]
+ case L'\u24C9': // [CIRCLED LATIN CAPITAL LETTER T]
+ case L'\uA786': // [LATIN CAPITAL LETTER INSULAR T]
+ case L'\uFF34': // [FULLWIDTH LATIN CAPITAL LETTER T]
+ *out = 'T';
+ break;
+ case L'\u0163': // [LATIN SMALL LETTER T WITH CEDILLA]
+ case L'\u0165': // [LATIN SMALL LETTER T WITH CARON]
+ case L'\u0167': // [LATIN SMALL LETTER T WITH STROKE]
+ case L'\u01AB': // [LATIN SMALL LETTER T WITH PALATAL HOOK]
+ case L'\u01AD': // [LATIN SMALL LETTER T WITH HOOK]
+ case L'\u021B': // [LATIN SMALL LETTER T WITH COMMA BELOW]
+ case L'\u0236': // [LATIN SMALL LETTER T WITH CURL]
+ case L'\u0287': // [LATIN SMALL LETTER TURNED T]
+ case L'\u0288': // [LATIN SMALL LETTER T WITH RETROFLEX HOOK]
+ case L'\u1D75': // [LATIN SMALL LETTER T WITH MIDDLE TILDE]
+ case L'\u1E6B': // [LATIN SMALL LETTER T WITH DOT ABOVE]
+ case L'\u1E6D': // [LATIN SMALL LETTER T WITH DOT BELOW]
+ case L'\u1E6F': // [LATIN SMALL LETTER T WITH LINE BELOW]
+ case L'\u1E71': // [LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW]
+ case L'\u1E97': // [LATIN SMALL LETTER T WITH DIAERESIS]
+ case L'\u24E3': // [CIRCLED LATIN SMALL LETTER T]
+ case L'\u2C66': // [LATIN SMALL LETTER T WITH DIAGONAL STROKE]
+ case L'\uFF54': // [FULLWIDTH LATIN SMALL LETTER T]
+ *out = 't';
+ break;
+ case L'\u00DE': // [LATIN CAPITAL LETTER THORN]
+ case L'\uA766': // [LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER]
+ *out = 'T';
+ *out = 'H';
+ break;
+ case L'\uA728': // [LATIN CAPITAL LETTER TZ]
+ *out = 'T';
+ *out = 'Z';
+ break;
+ case L'\u24AF': // [PARENTHESIZED LATIN SMALL LETTER T]
+ *out = '(';
+ *out = 't';
+ *out = ')';
+ break;
+ case L'\u02A8': // [LATIN SMALL LETTER TC DIGRAPH WITH CURL]
+ *out = 't';
+ *out = 'c';
+ break;
+ case L'\u00FE': // [LATIN SMALL LETTER THORN]
+ case L'\u1D7A': // [LATIN SMALL LETTER TH WITH STRIKETHROUGH]
+ case L'\uA767': // [LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER]
+ *out = 't';
+ *out = 'h';
+ break;
+ case L'\u02A6': // [LATIN SMALL LETTER TS DIGRAPH]
+ *out = 't';
+ *out = 's';
+ break;
+ case L'\uA729': // [LATIN SMALL LETTER TZ]
+ *out = 't';
+ *out = 'z';
+ break;
+ case L'\u00D9': // [LATIN CAPITAL LETTER U WITH GRAVE]
+ case L'\u00DA': // [LATIN CAPITAL LETTER U WITH ACUTE]
+ case L'\u00DB': // [LATIN CAPITAL LETTER U WITH CIRCUMFLEX]
+ case L'\u00DC': // [LATIN CAPITAL LETTER U WITH DIAERESIS]
+ case L'\u0168': // [LATIN CAPITAL LETTER U WITH TILDE]
+ case L'\u016A': // [LATIN CAPITAL LETTER U WITH MACRON]
+ case L'\u016C': // [LATIN CAPITAL LETTER U WITH BREVE]
+ case L'\u016E': // [LATIN CAPITAL LETTER U WITH RING ABOVE]
+ case L'\u0170': // [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE]
+ case L'\u0172': // [LATIN CAPITAL LETTER U WITH OGONEK]
+ case L'\u01AF': // [LATIN CAPITAL LETTER U WITH HORN]
+ case L'\u01D3': // [LATIN CAPITAL LETTER U WITH CARON]
+ case L'\u01D5': // [LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON]
+ case L'\u01D7': // [LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE]
+ case L'\u01D9': // [LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON]
+ case L'\u01DB': // [LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE]
+ case L'\u0214': // [LATIN CAPITAL LETTER U WITH DOUBLE GRAVE]
+ case L'\u0216': // [LATIN CAPITAL LETTER U WITH INVERTED BREVE]
+ case L'\u0244': // [LATIN CAPITAL LETTER U BAR]
+ case L'\u1D1C': // [LATIN LETTER SMALL CAPITAL U]
+ case L'\u1D7E': // [LATIN SMALL CAPITAL LETTER U WITH STROKE]
+ case L'\u1E72': // [LATIN CAPITAL LETTER U WITH DIAERESIS BELOW]
+ case L'\u1E74': // [LATIN CAPITAL LETTER U WITH TILDE BELOW]
+ case L'\u1E76': // [LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW]
+ case L'\u1E78': // [LATIN CAPITAL LETTER U WITH TILDE AND ACUTE]
+ case L'\u1E7A': // [LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS]
+ case L'\u1EE4': // [LATIN CAPITAL LETTER U WITH DOT BELOW]
+ case L'\u1EE6': // [LATIN CAPITAL LETTER U WITH HOOK ABOVE]
+ case L'\u1EE8': // [LATIN CAPITAL LETTER U WITH HORN AND ACUTE]
+ case L'\u1EEA': // [LATIN CAPITAL LETTER U WITH HORN AND GRAVE]
+ case L'\u1EEC': // [LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE]
+ case L'\u1EEE': // [LATIN CAPITAL LETTER U WITH HORN AND TILDE]
+ case L'\u1EF0': // [LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW]
+ case L'\u24CA': // [CIRCLED LATIN CAPITAL LETTER U]
+ case L'\uFF35': // [FULLWIDTH LATIN CAPITAL LETTER U]
+ *out = 'U';
+ break;
+ case L'\u00F9': // [LATIN SMALL LETTER U WITH GRAVE]
+ case L'\u00FA': // [LATIN SMALL LETTER U WITH ACUTE]
+ case L'\u00FB': // [LATIN SMALL LETTER U WITH CIRCUMFLEX]
+ case L'\u00FC': // [LATIN SMALL LETTER U WITH DIAERESIS]
+ case L'\u0169': // [LATIN SMALL LETTER U WITH TILDE]
+ case L'\u016B': // [LATIN SMALL LETTER U WITH MACRON]
+ case L'\u016D': // [LATIN SMALL LETTER U WITH BREVE]
+ case L'\u016F': // [LATIN SMALL LETTER U WITH RING ABOVE]
+ case L'\u0171': // [LATIN SMALL LETTER U WITH DOUBLE ACUTE]
+ case L'\u0173': // [LATIN SMALL LETTER U WITH OGONEK]
+ case L'\u01B0': // [LATIN SMALL LETTER U WITH HORN]
+ case L'\u01D4': // [LATIN SMALL LETTER U WITH CARON]
+ case L'\u01D6': // [LATIN SMALL LETTER U WITH DIAERESIS AND MACRON]
+ case L'\u01D8': // [LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE]
+ case L'\u01DA': // [LATIN SMALL LETTER U WITH DIAERESIS AND CARON]
+ case L'\u01DC': // [LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE]
+ case L'\u0215': // [LATIN SMALL LETTER U WITH DOUBLE GRAVE]
+ case L'\u0217': // [LATIN SMALL LETTER U WITH INVERTED BREVE]
+ case L'\u0289': // [LATIN SMALL LETTER U BAR]
+ case L'\u1D64': // [LATIN SUBSCRIPT SMALL LETTER U]
+ case L'\u1D99': // [LATIN SMALL LETTER U WITH RETROFLEX HOOK]
+ case L'\u1E73': // [LATIN SMALL LETTER U WITH DIAERESIS BELOW]
+ case L'\u1E75': // [LATIN SMALL LETTER U WITH TILDE BELOW]
+ case L'\u1E77': // [LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW]
+ case L'\u1E79': // [LATIN SMALL LETTER U WITH TILDE AND ACUTE]
+ case L'\u1E7B': // [LATIN SMALL LETTER U WITH MACRON AND DIAERESIS]
+ case L'\u1EE5': // [LATIN SMALL LETTER U WITH DOT BELOW]
+ case L'\u1EE7': // [LATIN SMALL LETTER U WITH HOOK ABOVE]
+ case L'\u1EE9': // [LATIN SMALL LETTER U WITH HORN AND ACUTE]
+ case L'\u1EEB': // [LATIN SMALL LETTER U WITH HORN AND GRAVE]
+ case L'\u1EED': // [LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE]
+ case L'\u1EEF': // [LATIN SMALL LETTER U WITH HORN AND TILDE]
+ case L'\u1EF1': // [LATIN SMALL LETTER U WITH HORN AND DOT BELOW]
+ case L'\u24E4': // [CIRCLED LATIN SMALL LETTER U]
+ case L'\uFF55': // [FULLWIDTH LATIN SMALL LETTER U]
+ *out = 'u';
+ break;
+ case L'\u24B0': // [PARENTHESIZED LATIN SMALL LETTER U]
+ *out = '(';
+ *out = 'u';
+ *out = ')';
+ break;
+ case L'\u1D6B': // [LATIN SMALL LETTER UE]
+ *out = 'u';
+ *out = 'e';
+ break;
+ case L'\u01B2': // [LATIN CAPITAL LETTER V WITH HOOK]
+ case L'\u0245': // [LATIN CAPITAL LETTER TURNED V]
+ case L'\u1D20': // [LATIN LETTER SMALL CAPITAL V]
+ case L'\u1E7C': // [LATIN CAPITAL LETTER V WITH TILDE]
+ case L'\u1E7E': // [LATIN CAPITAL LETTER V WITH DOT BELOW]
+ case L'\u1EFC': // [LATIN CAPITAL LETTER MIDDLE-WELSH V]
+ case L'\u24CB': // [CIRCLED LATIN CAPITAL LETTER V]
+ case L'\uA75E': // [LATIN CAPITAL LETTER V WITH DIAGONAL STROKE]
+ case L'\uA768': // [LATIN CAPITAL LETTER VEND]
+ case L'\uFF36': // [FULLWIDTH LATIN CAPITAL LETTER V]
+ *out = 'V';
+ break;
+ case L'\u028B': // [LATIN SMALL LETTER V WITH HOOK]
+ case L'\u028C': // [LATIN SMALL LETTER TURNED V]
+ case L'\u1D65': // [LATIN SUBSCRIPT SMALL LETTER V]
+ case L'\u1D8C': // [LATIN SMALL LETTER V WITH PALATAL HOOK]
+ case L'\u1E7D': // [LATIN SMALL LETTER V WITH TILDE]
+ case L'\u1E7F': // [LATIN SMALL LETTER V WITH DOT BELOW]
+ case L'\u24E5': // [CIRCLED LATIN SMALL LETTER V]
+ case L'\u2C71': // [LATIN SMALL LETTER V WITH RIGHT HOOK]
+ case L'\u2C74': // [LATIN SMALL LETTER V WITH CURL]
+ case L'\uA75F': // [LATIN SMALL LETTER V WITH DIAGONAL STROKE]
+ case L'\uFF56': // [FULLWIDTH LATIN SMALL LETTER V]
+ *out = 'v';
+ break;
+ case L'\uA760': // [LATIN CAPITAL LETTER VY]
+ *out = 'V';
+ *out = 'Y';
+ break;
+ case L'\u24B1': // [PARENTHESIZED LATIN SMALL LETTER V]
+ *out = '(';
+ *out = 'v';
+ *out = ')';
+ break;
+ case L'\uA761': // [LATIN SMALL LETTER VY]
+ *out = 'v';
+ *out = 'y';
+ break;
+ case L'\u0174': // [LATIN CAPITAL LETTER W WITH CIRCUMFLEX]
+ case L'\u01F7': // [LATIN CAPITAL LETTER WYNN]
+ case L'\u1D21': // [LATIN LETTER SMALL CAPITAL W]
+ case L'\u1E80': // [LATIN CAPITAL LETTER W WITH GRAVE]
+ case L'\u1E82': // [LATIN CAPITAL LETTER W WITH ACUTE]
+ case L'\u1E84': // [LATIN CAPITAL LETTER W WITH DIAERESIS]
+ case L'\u1E86': // [LATIN CAPITAL LETTER W WITH DOT ABOVE]
+ case L'\u1E88': // [LATIN CAPITAL LETTER W WITH DOT BELOW]
+ case L'\u24CC': // [CIRCLED LATIN CAPITAL LETTER W]
+ case L'\u2C72': // [LATIN CAPITAL LETTER W WITH HOOK]
+ case L'\uFF37': // [FULLWIDTH LATIN CAPITAL LETTER W]
+ *out = 'W';
+ break;
+ case L'\u0175': // [LATIN SMALL LETTER W WITH CIRCUMFLEX]
+ case L'\u01BF': // [LATIN LETTER WYNN]
+ case L'\u028D': // [LATIN SMALL LETTER TURNED W]
+ case L'\u1E81': // [LATIN SMALL LETTER W WITH GRAVE]
+ case L'\u1E83': // [LATIN SMALL LETTER W WITH ACUTE]
+ case L'\u1E85': // [LATIN SMALL LETTER W WITH DIAERESIS]
+ case L'\u1E87': // [LATIN SMALL LETTER W WITH DOT ABOVE]
+ case L'\u1E89': // [LATIN SMALL LETTER W WITH DOT BELOW]
+ case L'\u1E98': // [LATIN SMALL LETTER W WITH RING ABOVE]
+ case L'\u24E6': // [CIRCLED LATIN SMALL LETTER W]
+ case L'\u2C73': // [LATIN SMALL LETTER W WITH HOOK]
+ case L'\uFF57': // [FULLWIDTH LATIN SMALL LETTER W]
+ *out = 'w';
+ break;
+ case L'\u24B2': // [PARENTHESIZED LATIN SMALL LETTER W]
+ *out = '(';
+ *out = 'w';
+ *out = ')';
+ break;
+ case L'\u1E8A': // [LATIN CAPITAL LETTER X WITH DOT ABOVE]
+ case L'\u1E8C': // [LATIN CAPITAL LETTER X WITH DIAERESIS]
+ case L'\u24CD': // [CIRCLED LATIN CAPITAL LETTER X]
+ case L'\uFF38': // [FULLWIDTH LATIN CAPITAL LETTER X]
+ *out = 'X';
+ break;
+ case L'\u1D8D': // [LATIN SMALL LETTER X WITH PALATAL HOOK]
+ case L'\u1E8B': // [LATIN SMALL LETTER X WITH DOT ABOVE]
+ case L'\u1E8D': // [LATIN SMALL LETTER X WITH DIAERESIS]
+ case L'\u2093': // [LATIN SUBSCRIPT SMALL LETTER X]
+ case L'\u24E7': // [CIRCLED LATIN SMALL LETTER X]
+ case L'\uFF58': // [FULLWIDTH LATIN SMALL LETTER X]
+ *out = 'x';
+ break;
+ case L'\u24B3': // [PARENTHESIZED LATIN SMALL LETTER X]
+ *out = '(';
+ *out = 'x';
+ *out = ')';
+ break;
+ case L'\u00DD': // [LATIN CAPITAL LETTER Y WITH ACUTE]
+ case L'\u0176': // [LATIN CAPITAL LETTER Y WITH CIRCUMFLEX]
+ case L'\u0178': // [LATIN CAPITAL LETTER Y WITH DIAERESIS]
+ case L'\u01B3': // [LATIN CAPITAL LETTER Y WITH HOOK]
+ case L'\u0232': // [LATIN CAPITAL LETTER Y WITH MACRON]
+ case L'\u024E': // [LATIN CAPITAL LETTER Y WITH STROKE]
+ case L'\u028F': // [LATIN LETTER SMALL CAPITAL Y]
+ case L'\u1E8E': // [LATIN CAPITAL LETTER Y WITH DOT ABOVE]
+ case L'\u1EF2': // [LATIN CAPITAL LETTER Y WITH GRAVE]
+ case L'\u1EF4': // [LATIN CAPITAL LETTER Y WITH DOT BELOW]
+ case L'\u1EF6': // [LATIN CAPITAL LETTER Y WITH HOOK ABOVE]
+ case L'\u1EF8': // [LATIN CAPITAL LETTER Y WITH TILDE]
+ case L'\u1EFE': // [LATIN CAPITAL LETTER Y WITH LOOP]
+ case L'\u24CE': // [CIRCLED LATIN CAPITAL LETTER Y]
+ case L'\uFF39': // [FULLWIDTH LATIN CAPITAL LETTER Y]
+ *out = 'Y';
+ break;
+ case L'\u00FD': // [LATIN SMALL LETTER Y WITH ACUTE]
+ case L'\u00FF': // [LATIN SMALL LETTER Y WITH DIAERESIS]
+ case L'\u0177': // [LATIN SMALL LETTER Y WITH CIRCUMFLEX]
+ case L'\u01B4': // [LATIN SMALL LETTER Y WITH HOOK]
+ case L'\u0233': // [LATIN SMALL LETTER Y WITH MACRON]
+ case L'\u024F': // [LATIN SMALL LETTER Y WITH STROKE]
+ case L'\u028E': // [LATIN SMALL LETTER TURNED Y]
+ case L'\u1E8F': // [LATIN SMALL LETTER Y WITH DOT ABOVE]
+ case L'\u1E99': // [LATIN SMALL LETTER Y WITH RING ABOVE]
+ case L'\u1EF3': // [LATIN SMALL LETTER Y WITH GRAVE]
+ case L'\u1EF5': // [LATIN SMALL LETTER Y WITH DOT BELOW]
+ case L'\u1EF7': // [LATIN SMALL LETTER Y WITH HOOK ABOVE]
+ case L'\u1EF9': // [LATIN SMALL LETTER Y WITH TILDE]
+ case L'\u1EFF': // [LATIN SMALL LETTER Y WITH LOOP]
+ case L'\u24E8': // [CIRCLED LATIN SMALL LETTER Y]
+ case L'\uFF59': // [FULLWIDTH LATIN SMALL LETTER Y]
+ *out = 'y';
+ break;
+ case L'\u24B4': // [PARENTHESIZED LATIN SMALL LETTER Y]
+ *out = '(';
+ *out = 'y';
+ *out = ')';
+ break;
+ case L'\u0179': // [LATIN CAPITAL LETTER Z WITH ACUTE]
+ case L'\u017B': // [LATIN CAPITAL LETTER Z WITH DOT ABOVE]
+ case L'\u017D': // [LATIN CAPITAL LETTER Z WITH CARON]
+ case L'\u01B5': // [LATIN CAPITAL LETTER Z WITH STROKE]
+ case L'\u021C': // [LATIN CAPITAL LETTER YOGH]
+ case L'\u0224': // [LATIN CAPITAL LETTER Z WITH HOOK]
+ case L'\u1D22': // [LATIN LETTER SMALL CAPITAL Z]
+ case L'\u1E90': // [LATIN CAPITAL LETTER Z WITH CIRCUMFLEX]
+ case L'\u1E92': // [LATIN CAPITAL LETTER Z WITH DOT BELOW]
+ case L'\u1E94': // [LATIN CAPITAL LETTER Z WITH LINE BELOW]
+ case L'\u24CF': // [CIRCLED LATIN CAPITAL LETTER Z]
+ case L'\u2C6B': // [LATIN CAPITAL LETTER Z WITH DESCENDER]
+ case L'\uA762': // [LATIN CAPITAL LETTER VISIGOTHIC Z]
+ case L'\uFF3A': // [FULLWIDTH LATIN CAPITAL LETTER Z]
+ *out = 'Z';
+ break;
+ case L'\u017A': // [LATIN SMALL LETTER Z WITH ACUTE]
+ case L'\u017C': // [LATIN SMALL LETTER Z WITH DOT ABOVE]
+ case L'\u017E': // [LATIN SMALL LETTER Z WITH CARON]
+ case L'\u01B6': // [LATIN SMALL LETTER Z WITH STROKE]
+ case L'\u021D': // [LATIN SMALL LETTER YOGH]
+ case L'\u0225': // [LATIN SMALL LETTER Z WITH HOOK]
+ case L'\u0240': // [LATIN SMALL LETTER Z WITH SWASH TAIL]
+ case L'\u0290': // [LATIN SMALL LETTER Z WITH RETROFLEX HOOK]
+ case L'\u0291': // [LATIN SMALL LETTER Z WITH CURL]
+ case L'\u1D76': // [LATIN SMALL LETTER Z WITH MIDDLE TILDE]
+ case L'\u1D8E': // [LATIN SMALL LETTER Z WITH PALATAL HOOK]
+ case L'\u1E91': // [LATIN SMALL LETTER Z WITH CIRCUMFLEX]
+ case L'\u1E93': // [LATIN SMALL LETTER Z WITH DOT BELOW]
+ case L'\u1E95': // [LATIN SMALL LETTER Z WITH LINE BELOW]
+ case L'\u24E9': // [CIRCLED LATIN SMALL LETTER Z]
+ case L'\u2C6C': // [LATIN SMALL LETTER Z WITH DESCENDER]
+ case L'\uA763': // [LATIN SMALL LETTER VISIGOTHIC Z]
+ case L'\uFF5A': // [FULLWIDTH LATIN SMALL LETTER Z]
+ *out = 'z';
+ break;
+ case L'\u24B5': // [PARENTHESIZED LATIN SMALL LETTER Z]
+ *out = '(';
+ *out = 'z';
+ *out = ')';
+ break;
+ case L'\u2070': // [SUPERSCRIPT ZERO]
+ case L'\u2080': // [SUBSCRIPT ZERO]
+ case L'\u24EA': // [CIRCLED DIGIT ZERO]
+ case L'\u24FF': // [NEGATIVE CIRCLED DIGIT ZERO]
+ case L'\uFF10': // [FULLWIDTH DIGIT ZERO]
+ *out = '0';
+ break;
+ case L'\u00B9': // [SUPERSCRIPT ONE]
+ case L'\u2081': // [SUBSCRIPT ONE]
+ case L'\u2460': // [CIRCLED DIGIT ONE]
+ case L'\u24F5': // [DOUBLE CIRCLED DIGIT ONE]
+ case L'\u2776': // [DINGBAT NEGATIVE CIRCLED DIGIT ONE]
+ case L'\u2780': // [DINGBAT CIRCLED SANS-SERIF DIGIT ONE]
+ case L'\u278A': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE]
+ case L'\uFF11': // [FULLWIDTH DIGIT ONE]
+ *out = '1';
+ break;
+ case L'\u2488': // [DIGIT ONE FULL STOP]
+ *out = '1';
+ *out = '.';
+ break;
+ case L'\u2474': // [PARENTHESIZED DIGIT ONE]
+ *out = '(';
+ *out = '1';
+ *out = ')';
+ break;
+ case L'\u00B2': // [SUPERSCRIPT TWO]
+ case L'\u2082': // [SUBSCRIPT TWO]
+ case L'\u2461': // [CIRCLED DIGIT TWO]
+ case L'\u24F6': // [DOUBLE CIRCLED DIGIT TWO]
+ case L'\u2777': // [DINGBAT NEGATIVE CIRCLED DIGIT TWO]
+ case L'\u2781': // [DINGBAT CIRCLED SANS-SERIF DIGIT TWO]
+ case L'\u278B': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO]
+ case L'\uFF12': // [FULLWIDTH DIGIT TWO]
+ *out = '2';
+ break;
+ case L'\u2489': // [DIGIT TWO FULL STOP]
+ *out = '2';
+ *out = '.';
+ break;
+ case L'\u2475': // [PARENTHESIZED DIGIT TWO]
+ *out = '(';
+ *out = '2';
+ *out = ')';
+ break;
+ case L'\u00B3': // [SUPERSCRIPT THREE]
+ case L'\u2083': // [SUBSCRIPT THREE]
+ case L'\u2462': // [CIRCLED DIGIT THREE]
+ case L'\u24F7': // [DOUBLE CIRCLED DIGIT THREE]
+ case L'\u2778': // [DINGBAT NEGATIVE CIRCLED DIGIT THREE]
+ case L'\u2782': // [DINGBAT CIRCLED SANS-SERIF DIGIT THREE]
+ case L'\u278C': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE]
+ case L'\uFF13': // [FULLWIDTH DIGIT THREE]
+ *out = '3';
+ break;
+ case L'\u248A': // [DIGIT THREE FULL STOP]
+ *out = '3';
+ *out = '.';
+ break;
+ case L'\u2476': // [PARENTHESIZED DIGIT THREE]
+ *out = '(';
+ *out = '3';
+ *out = ')';
+ break;
+ case L'\u2074': // [SUPERSCRIPT FOUR]
+ case L'\u2084': // [SUBSCRIPT FOUR]
+ case L'\u2463': // [CIRCLED DIGIT FOUR]
+ case L'\u24F8': // [DOUBLE CIRCLED DIGIT FOUR]
+ case L'\u2779': // [DINGBAT NEGATIVE CIRCLED DIGIT FOUR]
+ case L'\u2783': // [DINGBAT CIRCLED SANS-SERIF DIGIT FOUR]
+ case L'\u278D': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR]
+ case L'\uFF14': // [FULLWIDTH DIGIT FOUR]
+ *out = '4';
+ break;
+ case L'\u248B': // [DIGIT FOUR FULL STOP]
+ *out = '4';
+ *out = '.';
+ break;
+ case L'\u2477': // [PARENTHESIZED DIGIT FOUR]
+ *out = '(';
+ *out = '4';
+ *out = ')';
+ break;
+ case L'\u2075': // [SUPERSCRIPT FIVE]
+ case L'\u2085': // [SUBSCRIPT FIVE]
+ case L'\u2464': // [CIRCLED DIGIT FIVE]
+ case L'\u24F9': // [DOUBLE CIRCLED DIGIT FIVE]
+ case L'\u277A': // [DINGBAT NEGATIVE CIRCLED DIGIT FIVE]
+ case L'\u2784': // [DINGBAT CIRCLED SANS-SERIF DIGIT FIVE]
+ case L'\u278E': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE]
+ case L'\uFF15': // [FULLWIDTH DIGIT FIVE]
+ *out = '5';
+ break;
+ case L'\u248C': // [DIGIT FIVE FULL STOP]
+ *out = '5';
+ *out = '.';
+ break;
+ case L'\u2478': // [PARENTHESIZED DIGIT FIVE]
+ *out = '(';
+ *out = '5';
+ *out = ')';
+ break;
+ case L'\u2076': // [SUPERSCRIPT SIX]
+ case L'\u2086': // [SUBSCRIPT SIX]
+ case L'\u2465': // [CIRCLED DIGIT SIX]
+ case L'\u24FA': // [DOUBLE CIRCLED DIGIT SIX]
+ case L'\u277B': // [DINGBAT NEGATIVE CIRCLED DIGIT SIX]
+ case L'\u2785': // [DINGBAT CIRCLED SANS-SERIF DIGIT SIX]
+ case L'\u278F': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX]
+ case L'\uFF16': // [FULLWIDTH DIGIT SIX]
+ *out = '6';
+ break;
+ case L'\u248D': // [DIGIT SIX FULL STOP]
+ *out = '6';
+ *out = '.';
+ break;
+ case L'\u2479': // [PARENTHESIZED DIGIT SIX]
+ *out = '(';
+ *out = '6';
+ *out = ')';
+ break;
+ case L'\u2077': // [SUPERSCRIPT SEVEN]
+ case L'\u2087': // [SUBSCRIPT SEVEN]
+ case L'\u2466': // [CIRCLED DIGIT SEVEN]
+ case L'\u24FB': // [DOUBLE CIRCLED DIGIT SEVEN]
+ case L'\u277C': // [DINGBAT NEGATIVE CIRCLED DIGIT SEVEN]
+ case L'\u2786': // [DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN]
+ case L'\u2790': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN]
+ case L'\uFF17': // [FULLWIDTH DIGIT SEVEN]
+ *out = '7';
+ break;
+ case L'\u248E': // [DIGIT SEVEN FULL STOP]
+ *out = '7';
+ *out = '.';
+ break;
+ case L'\u247A': // [PARENTHESIZED DIGIT SEVEN]
+ *out = '(';
+ *out = '7';
+ *out = ')';
+ break;
+ case L'\u2078': // [SUPERSCRIPT EIGHT]
+ case L'\u2088': // [SUBSCRIPT EIGHT]
+ case L'\u2467': // [CIRCLED DIGIT EIGHT]
+ case L'\u24FC': // [DOUBLE CIRCLED DIGIT EIGHT]
+ case L'\u277D': // [DINGBAT NEGATIVE CIRCLED DIGIT EIGHT]
+ case L'\u2787': // [DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT]
+ case L'\u2791': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT]
+ case L'\uFF18': // [FULLWIDTH DIGIT EIGHT]
+ *out = '8';
+ break;
+ case L'\u248F': // [DIGIT EIGHT FULL STOP]
+ *out = '8';
+ *out = '.';
+ break;
+ case L'\u247B': // [PARENTHESIZED DIGIT EIGHT]
+ *out = '(';
+ *out = '8';
+ *out = ')';
+ break;
+ case L'\u2079': // [SUPERSCRIPT NINE]
+ case L'\u2089': // [SUBSCRIPT NINE]
+ case L'\u2468': // [CIRCLED DIGIT NINE]
+ case L'\u24FD': // [DOUBLE CIRCLED DIGIT NINE]
+ case L'\u277E': // [DINGBAT NEGATIVE CIRCLED DIGIT NINE]
+ case L'\u2788': // [DINGBAT CIRCLED SANS-SERIF DIGIT NINE]
+ case L'\u2792': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE]
+ case L'\uFF19': // [FULLWIDTH DIGIT NINE]
+ *out = '9';
+ break;
+ case L'\u2490': // [DIGIT NINE FULL STOP]
+ *out = '9';
+ *out = '.';
+ break;
+ case L'\u247C': // [PARENTHESIZED DIGIT NINE]
+ *out = '(';
+ *out = '9';
+ *out = ')';
+ break;
+ case L'\u2469': // [CIRCLED NUMBER TEN]
+ case L'\u24FE': // [DOUBLE CIRCLED NUMBER TEN]
+ case L'\u277F': // [DINGBAT NEGATIVE CIRCLED NUMBER TEN]
+ case L'\u2789': // [DINGBAT CIRCLED SANS-SERIF NUMBER TEN]
+ case L'\u2793': // [DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN]
+ *out = '1';
+ *out = '0';
+ break;
+ case L'\u2491': // [NUMBER TEN FULL STOP]
+ *out = '1';
+ *out = '0';
+ *out = '.';
+ break;
+ case L'\u247D': // [PARENTHESIZED NUMBER TEN]
+ *out = '(';
+ *out = '1';
+ *out = '0';
+ *out = ')';
+ break;
+ case L'\u246A': // [CIRCLED NUMBER ELEVEN]
+ case L'\u24EB': // [NEGATIVE CIRCLED NUMBER ELEVEN]
+ *out = '1';
+ *out = '1';
+ break;
+ case L'\u2492': // [NUMBER ELEVEN FULL STOP]
+ *out = '1';
+ *out = '1';
+ *out = '.';
+ break;
+ case L'\u247E': // [PARENTHESIZED NUMBER ELEVEN]
+ *out = '(';
+ *out = '1';
+ *out = '1';
+ *out = ')';
+ break;
+ case L'\u246B': // [CIRCLED NUMBER TWELVE]
+ case L'\u24EC': // [NEGATIVE CIRCLED NUMBER TWELVE]
+ *out = '1';
+ *out = '2';
+ break;
+ case L'\u2493': // [NUMBER TWELVE FULL STOP]
+ *out = '1';
+ *out = '2';
+ *out = '.';
+ break;
+ case L'\u247F': // [PARENTHESIZED NUMBER TWELVE]
+ *out = '(';
+ *out = '1';
+ *out = '2';
+ *out = ')';
+ break;
+ case L'\u246C': // [CIRCLED NUMBER THIRTEEN]
+ case L'\u24ED': // [NEGATIVE CIRCLED NUMBER THIRTEEN]
+ *out = '1';
+ *out = '3';
+ break;
+ case L'\u2494': // [NUMBER THIRTEEN FULL STOP]
+ *out = '1';
+ *out = '3';
+ *out = '.';
+ break;
+ case L'\u2480': // [PARENTHESIZED NUMBER THIRTEEN]
+ *out = '(';
+ *out = '1';
+ *out = '3';
+ *out = ')';
+ break;
+ case L'\u246D': // [CIRCLED NUMBER FOURTEEN]
+ case L'\u24EE': // [NEGATIVE CIRCLED NUMBER FOURTEEN]
+ *out = '1';
+ *out = '4';
+ break;
+ case L'\u2495': // [NUMBER FOURTEEN FULL STOP]
+ *out = '1';
+ *out = '4';
+ *out = '.';
+ break;
+ case L'\u2481': // [PARENTHESIZED NUMBER FOURTEEN]
+ *out = '(';
+ *out = '1';
+ *out = '4';
+ *out = ')';
+ break;
+ case L'\u246E': // [CIRCLED NUMBER FIFTEEN]
+ case L'\u24EF': // [NEGATIVE CIRCLED NUMBER FIFTEEN]
+ *out = '1';
+ *out = '5';
+ break;
+ case L'\u2496': // [NUMBER FIFTEEN FULL STOP]
+ *out = '1';
+ *out = '5';
+ *out = '.';
+ break;
+ case L'\u2482': // [PARENTHESIZED NUMBER FIFTEEN]
+ *out = '(';
+ *out = '1';
+ *out = '5';
+ *out = ')';
+ break;
+ case L'\u246F': // [CIRCLED NUMBER SIXTEEN]
+ case L'\u24F0': // [NEGATIVE CIRCLED NUMBER SIXTEEN]
+ *out = '1';
+ *out = '6';
+ break;
+ case L'\u2497': // [NUMBER SIXTEEN FULL STOP]
+ *out = '1';
+ *out = '6';
+ *out = '.';
+ break;
+ case L'\u2483': // [PARENTHESIZED NUMBER SIXTEEN]
+ *out = '(';
+ *out = '1';
+ *out = '6';
+ *out = ')';
+ break;
+ case L'\u2470': // [CIRCLED NUMBER SEVENTEEN]
+ case L'\u24F1': // [NEGATIVE CIRCLED NUMBER SEVENTEEN]
+ *out = '1';
+ *out = '7';
+ break;
+ case L'\u2498': // [NUMBER SEVENTEEN FULL STOP]
+ *out = '1';
+ *out = '7';
+ *out = '.';
+ break;
+ case L'\u2484': // [PARENTHESIZED NUMBER SEVENTEEN]
+ *out = '(';
+ *out = '1';
+ *out = '7';
+ *out = ')';
+ break;
+ case L'\u2471': // [CIRCLED NUMBER EIGHTEEN]
+ case L'\u24F2': // [NEGATIVE CIRCLED NUMBER EIGHTEEN]
+ *out = '1';
+ *out = '8';
+ break;
+ case L'\u2499': // [NUMBER EIGHTEEN FULL STOP]
+ *out = '1';
+ *out = '8';
+ *out = '.';
+ break;
+ case L'\u2485': // [PARENTHESIZED NUMBER EIGHTEEN]
+ *out = '(';
+ *out = '1';
+ *out = '8';
+ *out = ')';
+ break;
+ case L'\u2472': // [CIRCLED NUMBER NINETEEN]
+ case L'\u24F3': // [NEGATIVE CIRCLED NUMBER NINETEEN]
+ *out = '1';
+ *out = '9';
+ break;
+ case L'\u249A': // [NUMBER NINETEEN FULL STOP]
+ *out = '1';
+ *out = '9';
+ *out = '.';
+ break;
+ case L'\u2486': // [PARENTHESIZED NUMBER NINETEEN]
+ *out = '(';
+ *out = '1';
+ *out = '9';
+ *out = ')';
+ break;
+ case L'\u2473': // [CIRCLED NUMBER TWENTY]
+ case L'\u24F4': // [NEGATIVE CIRCLED NUMBER TWENTY]
+ *out = '2';
+ *out = '0';
+ break;
+ case L'\u249B': // [NUMBER TWENTY FULL STOP]
+ *out = '2';
+ *out = '0';
+ *out = '.';
+ break;
+ case L'\u2487': // [PARENTHESIZED NUMBER TWENTY]
+ *out = '(';
+ *out = '2';
+ *out = '0';
+ *out = ')';
+ break;
+ case L'\u00AB': // [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK]
+ case L'\u00BB': // [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK]
+ case L'\u201C': // [LEFT DOUBLE QUOTATION MARK]
+ case L'\u201D': // [RIGHT DOUBLE QUOTATION MARK]
+ case L'\u201E': // [DOUBLE LOW-9 QUOTATION MARK]
+ case L'\u2033': // [DOUBLE PRIME]
+ case L'\u2036': // [REVERSED DOUBLE PRIME]
+ case L'\u275D': // [HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT]
+ case L'\u275E': // [HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT]
+ case L'\u276E': // [HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT]
+ case L'\u276F': // [HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT]
+ case L'\uFF02': // [FULLWIDTH QUOTATION MARK]
+ *out = '"';
+ break;
+ case L'\u2018': // [LEFT SINGLE QUOTATION MARK]
+ case L'\u2019': // [RIGHT SINGLE QUOTATION MARK]
+ case L'\u201A': // [SINGLE LOW-9 QUOTATION MARK]
+ case L'\u201B': // [SINGLE HIGH-REVERSED-9 QUOTATION MARK]
+ case L'\u2032': // [PRIME]
+ case L'\u2035': // [REVERSED PRIME]
+ case L'\u2039': // [SINGLE LEFT-POINTING ANGLE QUOTATION MARK]
+ case L'\u203A': // [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK]
+ case L'\u275B': // [HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT]
+ case L'\u275C': // [HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT]
+ case L'\uFF07': // [FULLWIDTH APOSTROPHE]
+ *out = '\'';
+ break;
+ case L'\u2010': // [HYPHEN]
+ case L'\u2011': // [NON-BREAKING HYPHEN]
+ case L'\u2012': // [FIGURE DASH]
+ case L'\u2013': // [EN DASH]
+ case L'\u2014': // [EM DASH]
+ case L'\u207B': // [SUPERSCRIPT MINUS]
+ case L'\u208B': // [SUBSCRIPT MINUS]
+ case L'\uFF0D': // [FULLWIDTH HYPHEN-MINUS]
+ *out = '-';
+ break;
+ case L'\u2045': // [LEFT SQUARE BRACKET WITH QUILL]
+ case L'\u2772': // [LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT]
+ case L'\uFF3B': // [FULLWIDTH LEFT SQUARE BRACKET]
+ *out = '[';
+ break;
+ case L'\u2046': // [RIGHT SQUARE BRACKET WITH QUILL]
+ case L'\u2773': // [LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT]
+ case L'\uFF3D': // [FULLWIDTH RIGHT SQUARE BRACKET]
+ *out = ']';
+ break;
+ case L'\u207D': // [SUPERSCRIPT LEFT PARENTHESIS]
+ case L'\u208D': // [SUBSCRIPT LEFT PARENTHESIS]
+ case L'\u2768': // [MEDIUM LEFT PARENTHESIS ORNAMENT]
+ case L'\u276A': // [MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT]
+ case L'\uFF08': // [FULLWIDTH LEFT PARENTHESIS]
+ *out = '(';
+ break;
+ case L'\u2E28': // [LEFT DOUBLE PARENTHESIS]
+ *out = '(';
+ *out = '(';
+ break;
+ case L'\u207E': // [SUPERSCRIPT RIGHT PARENTHESIS]
+ case L'\u208E': // [SUBSCRIPT RIGHT PARENTHESIS]
+ case L'\u2769': // [MEDIUM RIGHT PARENTHESIS ORNAMENT]
+ case L'\u276B': // [MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT]
+ case L'\uFF09': // [FULLWIDTH RIGHT PARENTHESIS]
+ *out = ')';
+ break;
+ case L'\u2E29': // [RIGHT DOUBLE PARENTHESIS]
+ *out = ')';
+ *out = ')';
+ break;
+ case L'\u276C': // [MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT]
+ case L'\u2770': // [HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT]
+ case L'\uFF1C': // [FULLWIDTH LESS-THAN SIGN]
+ *out = '<';
+ break;
+ case L'\u276D': // [MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT]
+ case L'\u2771': // [HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT]
+ case L'\uFF1E': // [FULLWIDTH GREATER-THAN SIGN]
+ *out = '>';
+ break;
+ case L'\u2774': // [MEDIUM LEFT CURLY BRACKET ORNAMENT]
+ case L'\uFF5B': // [FULLWIDTH LEFT CURLY BRACKET]
+ *out = '{';
+ break;
+ case L'\u2775': // [MEDIUM RIGHT CURLY BRACKET ORNAMENT]
+ case L'\uFF5D': // [FULLWIDTH RIGHT CURLY BRACKET]
+ *out = '}';
+ break;
+ case L'\u207A': // [SUPERSCRIPT PLUS SIGN]
+ case L'\u208A': // [SUBSCRIPT PLUS SIGN]
+ case L'\uFF0B': // [FULLWIDTH PLUS SIGN]
+ *out = '+';
+ break;
+ case L'\u207C': // [SUPERSCRIPT EQUALS SIGN]
+ case L'\u208C': // [SUBSCRIPT EQUALS SIGN]
+ case L'\uFF1D': // [FULLWIDTH EQUALS SIGN]
+ *out = '=';
+ break;
+ case L'\uFF01': // [FULLWIDTH EXCLAMATION MARK]
+ *out = '!';
+ break;
+ case L'\u203C': // [DOUBLE EXCLAMATION MARK]
+ *out = '!';
+ *out = '!';
+ break;
+ case L'\u2049': // [EXCLAMATION QUESTION MARK]
+ *out = '!';
+ *out = '?';
+ break;
+ case L'\uFF03': // [FULLWIDTH NUMBER SIGN]
+ *out = '#';
+ break;
+ case L'\uFF04': // [FULLWIDTH DOLLAR SIGN]
+ *out = '$';
+ break;
+ case L'\u2052': // [COMMERCIAL MINUS SIGN]
+ case L'\uFF05': // [FULLWIDTH PERCENT SIGN]
+ *out = '%';
+ break;
+ case L'\uFF06': // [FULLWIDTH AMPERSAND]
+ *out = '&';
+ break;
+ case L'\u204E': // [LOW ASTERISK]
+ case L'\uFF0A': // [FULLWIDTH ASTERISK]
+ *out = '*';
+ break;
+ case L'\uFF0C': // [FULLWIDTH COMMA]
+ *out = ',';
+ break;
+ case L'\uFF0E': // [FULLWIDTH FULL STOP]
+ *out = '.';
+ break;
+ case L'\u2044': // [FRACTION SLASH]
+ case L'\uFF0F': // [FULLWIDTH SOLIDUS]
+ *out = '/';
+ break;
+ case L'\uFF1A': // [FULLWIDTH COLON]
+ *out = ':';
+ break;
+ case L'\u204F': // [REVERSED SEMICOLON]
+ case L'\uFF1B': // [FULLWIDTH SEMICOLON]
+ *out = ';';
+ break;
+ case L'\uFF1F': // [FULLWIDTH QUESTION MARK]
+ *out = '?';
+ break;
+ case L'\u2047': // [DOUBLE QUESTION MARK]
+ *out = '?';
+ *out = '?';
+ break;
+ case L'\u2048': // [QUESTION EXCLAMATION MARK]
+ *out = '?';
+ *out = '!';
+ break;
+ case L'\uFF20': // [FULLWIDTH COMMERCIAL AT]
+ *out = '@';
+ break;
+ case L'\uFF3C': // [FULLWIDTH REVERSE SOLIDUS]
+ *out = '\\';
+ break;
+ case L'\u2038': // [CARET]
+ case L'\uFF3E': // [FULLWIDTH CIRCUMFLEX ACCENT]
+ *out = '^';
+ break;
+ case L'\uFF3F': // [FULLWIDTH LOW LINE]
+ *out = '_';
+ break;
+ case L'\u2053': // [SWUNG DASH]
+ case L'\uFF5E': // [FULLWIDTH TILDE]
+ *out = '~';
+ break;
+ default:
+ *out = c;
+ break;
+ }
+ }
+}
+
+namespace Slic3r {
+
+std::string fold_utf8_to_ascii(const std::string &src)
+{
+ std::wstring wstr = boost::locale::conv::utf_to_utf<wchar_t>(src.c_str(), src.c_str() + src.size());
+ std::wstring dst;
+ dst.reserve(wstr.size());
+ auto out = std::back_insert_iterator<std::wstring>(dst);
+ for (wchar_t c : wstr)
+ fold_to_ascii(c, out);
+ return boost::locale::conv::utf_to_utf<char>(dst.c_str(), dst.c_str() + dst.size());
+}
+
+std::string fold_utf8_to_ascii(const char *src)
+{
+ std::wstring wstr = boost::locale::conv::utf_to_utf<wchar_t>(src, src + strlen(src));
+ std::wstring dst;
+ dst.reserve(wstr.size());
+ auto out = std::back_insert_iterator<std::wstring>(dst);
+ for (wchar_t c : wstr)
+ fold_to_ascii(c, out);
+ return boost::locale::conv::utf_to_utf<char>(dst.c_str(), dst.c_str() + dst.size());
+}
+
+}; // namespace Slic3r
diff --git a/src/slic3r/Utils/ASCIIFolding.hpp b/src/slic3r/Utils/ASCIIFolding.hpp
new file mode 100644
index 000000000..55f56482d
--- /dev/null
+++ b/src/slic3r/Utils/ASCIIFolding.hpp
@@ -0,0 +1,15 @@
+#ifndef slic3r_ASCIIFolding_hpp_
+#define slic3r_ASCIIFolding_hpp_
+
+#include <string>
+
+namespace Slic3r {
+
+// If possible, remove accents from accented latin characters.
+// This function is useful for generating file names to be processed by legacy firmwares.
+extern std::string fold_utf8_to_ascii(const char *src);
+extern std::string fold_utf8_to_ascii(const std::string &src);
+
+}; // namespace Slic3r
+
+#endif /* slic3r_ASCIIFolding_hpp_ */
diff --git a/src/slic3r/Utils/Bonjour.cpp b/src/slic3r/Utils/Bonjour.cpp
new file mode 100644
index 000000000..09d9b5873
--- /dev/null
+++ b/src/slic3r/Utils/Bonjour.cpp
@@ -0,0 +1,781 @@
+#include "Bonjour.hpp"
+
+#include <cstdint>
+#include <algorithm>
+#include <array>
+#include <vector>
+#include <string>
+#include <random>
+#include <thread>
+#include <boost/optional.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/endian/conversion.hpp>
+#include <boost/asio.hpp>
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+#include <boost/format.hpp>
+
+using boost::optional;
+using boost::system::error_code;
+namespace endian = boost::endian;
+namespace asio = boost::asio;
+using boost::asio::ip::udp;
+
+
+namespace Slic3r {
+
+
+// Minimal implementation of a MDNS/DNS-SD client
+// This implementation is extremely simple, only the bits that are useful
+// for basic MDNS discovery of OctoPi devices are present.
+// However, the bits that are present are implemented with security in mind.
+// Only fully correct DNS replies are allowed through.
+// While decoding the decoder will bail the moment it encounters anything fishy.
+// At least that's the idea. To help prove this is actually the case,
+// the implementations has been tested with AFL.
+
+
+struct DnsName: public std::string
+{
+ enum
+ {
+ MAX_RECURSION = 10, // Keep this low
+ };
+
+ static optional<DnsName> decode(const std::vector<char> &buffer, size_t &offset, unsigned depth = 0)
+ {
+ // Check offset sanity:
+ if (offset + 1 >= buffer.size()) {
+ return boost::none;
+ }
+
+ // Check for recursion depth to prevent parsing names that are nested too deeply or end up cyclic:
+ if (depth >= MAX_RECURSION) {
+ return boost::none;
+ }
+
+ DnsName res;
+ const size_t bsize = buffer.size();
+
+ while (true) {
+ const char* ptr = buffer.data() + offset;
+ unsigned len = static_cast<unsigned char>(*ptr);
+ if (len & 0xc0) {
+ // This is a recursive label
+ unsigned len_2 = static_cast<unsigned char>(ptr[1]);
+ size_t pointer = (len & 0x3f) << 8 | len_2;
+ const auto nested = decode(buffer, pointer, depth + 1);
+ if (!nested) {
+ return boost::none;
+ } else {
+ if (res.size() > 0) {
+ res.push_back('.');
+ }
+ res.append(*nested);
+ offset += 2;
+ return std::move(res);
+ }
+ } else if (len == 0) {
+ // This is a name terminator
+ offset++;
+ break;
+ } else {
+ // This is a regular label
+ len &= 0x3f;
+ if (len + offset + 1 >= bsize) {
+ return boost::none;
+ }
+
+ res.reserve(len);
+ if (res.size() > 0) {
+ res.push_back('.');
+ }
+
+ ptr++;
+ for (const auto end = ptr + len; ptr < end; ptr++) {
+ char c = *ptr;
+ if (c >= 0x20 && c <= 0x7f) {
+ res.push_back(c);
+ } else {
+ return boost::none;
+ }
+ }
+
+ offset += len + 1;
+ }
+ }
+
+ if (res.size() > 0) {
+ return std::move(res);
+ } else {
+ return boost::none;
+ }
+ }
+};
+
+struct DnsHeader
+{
+ uint16_t id;
+ uint16_t flags;
+ uint16_t qdcount;
+ uint16_t ancount;
+ uint16_t nscount;
+ uint16_t arcount;
+
+ enum
+ {
+ SIZE = 12,
+ };
+
+ static DnsHeader decode(const std::vector<char> &buffer) {
+ DnsHeader res;
+ const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data());
+ res.id = endian::big_to_native(data_16[0]);
+ res.flags = endian::big_to_native(data_16[1]);
+ res.qdcount = endian::big_to_native(data_16[2]);
+ res.ancount = endian::big_to_native(data_16[3]);
+ res.nscount = endian::big_to_native(data_16[4]);
+ res.arcount = endian::big_to_native(data_16[5]);
+ return res;
+ }
+
+ uint32_t rrcount() const {
+ return ancount + nscount + arcount;
+ }
+};
+
+struct DnsQuestion
+{
+ enum
+ {
+ MIN_SIZE = 5,
+ };
+
+ DnsName name;
+ uint16_t type;
+ uint16_t qclass;
+
+ DnsQuestion() :
+ type(0),
+ qclass(0)
+ {}
+
+ static optional<DnsQuestion> decode(const std::vector<char> &buffer, size_t &offset)
+ {
+ auto qname = DnsName::decode(buffer, offset);
+ if (!qname) {
+ return boost::none;
+ }
+
+ DnsQuestion res;
+ res.name = std::move(*qname);
+ const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
+ res.type = endian::big_to_native(data_16[0]);
+ res.qclass = endian::big_to_native(data_16[1]);
+
+ offset += 4;
+ return std::move(res);
+ }
+};
+
+struct DnsResource
+{
+ DnsName name;
+ uint16_t type;
+ uint16_t rclass;
+ uint32_t ttl;
+ std::vector<char> data;
+
+ DnsResource() :
+ type(0),
+ rclass(0),
+ ttl(0)
+ {}
+
+ static optional<DnsResource> decode(const std::vector<char> &buffer, size_t &offset, size_t &dataoffset)
+ {
+ const size_t bsize = buffer.size();
+ if (offset + 1 >= bsize) {
+ return boost::none;
+ }
+
+ auto rname = DnsName::decode(buffer, offset);
+ if (!rname) {
+ return boost::none;
+ }
+
+ if (offset + 10 >= bsize) {
+ return boost::none;
+ }
+
+ DnsResource res;
+ res.name = std::move(*rname);
+ const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(buffer.data() + offset);
+ res.type = endian::big_to_native(data_16[0]);
+ res.rclass = endian::big_to_native(data_16[1]);
+ res.ttl = endian::big_to_native(*reinterpret_cast<const uint32_t*>(data_16 + 2));
+ uint16_t rdlength = endian::big_to_native(data_16[4]);
+
+ offset += 10;
+ if (offset + rdlength > bsize) {
+ return boost::none;
+ }
+
+ dataoffset = offset;
+ res.data = std::move(std::vector<char>(buffer.begin() + offset, buffer.begin() + offset + rdlength));
+ offset += rdlength;
+
+ return std::move(res);
+ }
+};
+
+struct DnsRR_A
+{
+ enum { TAG = 0x1 };
+
+ asio::ip::address_v4 ip;
+
+ static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
+ {
+ if (rr.data.size() == 4) {
+ DnsRR_A res;
+ const uint32_t ip = endian::big_to_native(*reinterpret_cast<const uint32_t*>(rr.data.data()));
+ res.ip = asio::ip::address_v4(ip);
+ result = std::move(res);
+ }
+ }
+};
+
+struct DnsRR_AAAA
+{
+ enum { TAG = 0x1c };
+
+ asio::ip::address_v6 ip;
+
+ static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
+ {
+ if (rr.data.size() == 16) {
+ DnsRR_AAAA res;
+ std::array<unsigned char, 16> ip;
+ std::copy_n(rr.data.begin(), 16, ip.begin());
+ res.ip = asio::ip::address_v6(ip);
+ result = std::move(res);
+ }
+ }
+};
+
+struct DnsRR_SRV
+{
+ enum
+ {
+ TAG = 0x21,
+ MIN_SIZE = 8,
+ };
+
+ uint16_t priority;
+ uint16_t weight;
+ uint16_t port;
+ DnsName hostname;
+
+ static optional<DnsRR_SRV> decode(const std::vector<char> &buffer, const DnsResource &rr, size_t dataoffset)
+ {
+ if (rr.data.size() < MIN_SIZE) {
+ return boost::none;
+ }
+
+ DnsRR_SRV res;
+
+ const uint16_t *data_16 = reinterpret_cast<const uint16_t*>(rr.data.data());
+ res.priority = endian::big_to_native(data_16[0]);
+ res.weight = endian::big_to_native(data_16[1]);
+ res.port = endian::big_to_native(data_16[2]);
+
+ size_t offset = dataoffset + 6;
+ auto hostname = DnsName::decode(buffer, offset);
+
+ if (hostname) {
+ res.hostname = std::move(*hostname);
+ return std::move(res);
+ } else {
+ return boost::none;
+ }
+ }
+};
+
+struct DnsRR_TXT
+{
+ enum
+ {
+ TAG = 0x10,
+ };
+
+ std::vector<std::string> values;
+
+ static optional<DnsRR_TXT> decode(const DnsResource &rr)
+ {
+ const size_t size = rr.data.size();
+ if (size < 2) {
+ return boost::none;
+ }
+
+ DnsRR_TXT res;
+
+ for (auto it = rr.data.begin(); it != rr.data.end(); ) {
+ unsigned val_size = static_cast<unsigned char>(*it);
+ if (val_size == 0 || it + val_size >= rr.data.end()) {
+ return boost::none;
+ }
+ ++it;
+
+ std::string value(val_size, ' ');
+ std::copy(it, it + val_size, value.begin());
+ res.values.push_back(std::move(value));
+
+ it += val_size;
+ }
+
+ return std::move(res);
+ }
+};
+
+struct DnsSDPair
+{
+ optional<DnsRR_SRV> srv;
+ optional<DnsRR_TXT> txt;
+};
+
+struct DnsSDMap : public std::map<std::string, DnsSDPair>
+{
+ void insert_srv(std::string &&name, DnsRR_SRV &&srv)
+ {
+ auto hit = this->find(name);
+ if (hit != this->end()) {
+ hit->second.srv = std::move(srv);
+ } else {
+ DnsSDPair pair;
+ pair.srv = std::move(srv);
+ this->insert(std::make_pair(std::move(name), std::move(pair)));
+ }
+ }
+
+ void insert_txt(std::string &&name, DnsRR_TXT &&txt)
+ {
+ auto hit = this->find(name);
+ if (hit != this->end()) {
+ hit->second.txt = std::move(txt);
+ } else {
+ DnsSDPair pair;
+ pair.txt = std::move(txt);
+ this->insert(std::make_pair(std::move(name), std::move(pair)));
+ }
+ }
+};
+
+struct DnsMessage
+{
+ enum
+ {
+ MAX_SIZE = 4096,
+ MAX_ANS = 30,
+ };
+
+ DnsHeader header;
+ optional<DnsQuestion> question;
+
+ optional<DnsRR_A> rr_a;
+ optional<DnsRR_AAAA> rr_aaaa;
+ std::vector<DnsRR_SRV> rr_srv;
+
+ DnsSDMap sdmap;
+
+ static optional<DnsMessage> decode(const std::vector<char> &buffer, optional<uint16_t> id_wanted = boost::none)
+ {
+ const auto size = buffer.size();
+ if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
+ return boost::none;
+ }
+
+ DnsMessage res;
+ res.header = DnsHeader::decode(buffer);
+
+ if (id_wanted && *id_wanted != res.header.id) {
+ return boost::none;
+ }
+
+ if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) {
+ return boost::none;
+ }
+
+ size_t offset = DnsHeader::SIZE;
+ if (res.header.qdcount == 1) {
+ res.question = DnsQuestion::decode(buffer, offset);
+ }
+
+ for (unsigned i = 0; i < res.header.rrcount(); i++) {
+ size_t dataoffset = 0;
+ auto rr = DnsResource::decode(buffer, offset, dataoffset);
+ if (!rr) {
+ return boost::none;
+ } else {
+ res.parse_rr(buffer, std::move(*rr), dataoffset);
+ }
+ }
+
+ return std::move(res);
+ }
+private:
+ void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset)
+ {
+ switch (rr.type) {
+ case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
+ case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
+ case DnsRR_SRV::TAG: {
+ auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
+ if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
+ break;
+ }
+ case DnsRR_TXT::TAG: {
+ auto txt = DnsRR_TXT::decode(rr);
+ if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); }
+ break;
+ }
+ }
+ }
+};
+
+std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
+{
+ os << "DnsMessage(ID: " << msg.header.id << ", "
+ << "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", "
+ << "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", "
+ << "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", "
+ << "services: [";
+
+ enum { SRV_PRINT_MAX = 3 };
+ unsigned i = 0;
+ for (const auto &sdpair : msg.sdmap) {
+ os << sdpair.first << ", ";
+
+ if (++i >= SRV_PRINT_MAX) {
+ os << "...";
+ break;
+ }
+ }
+
+ os << "])";
+
+ return os;
+}
+
+
+struct BonjourRequest
+{
+ static const asio::ip::address_v4 MCAST_IP4;
+ static const uint16_t MCAST_PORT;
+
+ uint16_t id;
+ std::vector<char> data;
+
+ static optional<BonjourRequest> make(const std::string &service, const std::string &protocol);
+
+private:
+ BonjourRequest(uint16_t id, std::vector<char> &&data) :
+ id(id),
+ data(std::move(data))
+ {}
+};
+
+const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
+const uint16_t BonjourRequest::MCAST_PORT = 5353;
+
+optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
+{
+ if (service.size() > 15 || protocol.size() > 15) {
+ return boost::none;
+ }
+
+ std::random_device dev;
+ std::uniform_int_distribution<uint16_t> dist;
+ uint16_t id = dist(dev);
+ uint16_t id_big = endian::native_to_big(id);
+ const char *id_char = reinterpret_cast<char*>(&id_big);
+
+ std::vector<char> data;
+ data.reserve(service.size() + 18);
+
+ // Add the transaction ID
+ data.push_back(id_char[0]);
+ data.push_back(id_char[1]);
+
+ // Add metadata
+ static const unsigned char rq_meta[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
+
+ // Add PTR query name
+ data.push_back(service.size() + 1);
+ data.push_back('_');
+ data.insert(data.end(), service.begin(), service.end());
+ data.push_back(protocol.size() + 1);
+ data.push_back('_');
+ data.insert(data.end(), protocol.begin(), protocol.end());
+
+ // Add the rest of PTR record
+ static const unsigned char ptr_tail[] = {
+ 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff,
+ };
+ std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
+
+ return BonjourRequest(id, std::move(data));
+}
+
+
+// API - private part
+
+struct Bonjour::priv
+{
+ const std::string service;
+ const std::string protocol;
+ const std::string service_dn;
+ unsigned timeout;
+ unsigned retries;
+ uint16_t rq_id;
+
+ std::vector<char> buffer;
+ std::thread io_thread;
+ Bonjour::ReplyFn replyfn;
+ Bonjour::CompleteFn completefn;
+
+ priv(std::string service, std::string protocol);
+
+ std::string strip_service_dn(const std::string &service_name) const;
+ void udp_receive(udp::endpoint from, size_t bytes);
+ void lookup_perform();
+};
+
+Bonjour::priv::priv(std::string service, std::string protocol) :
+ service(std::move(service)),
+ protocol(std::move(protocol)),
+ service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()),
+ timeout(10),
+ retries(1),
+ rq_id(0)
+{
+ buffer.resize(DnsMessage::MAX_SIZE);
+}
+
+std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
+{
+ if (service_name.size() <= service_dn.size()) {
+ return service_name;
+ }
+
+ auto needle = service_name.rfind(service_dn);
+ if (needle == service_name.size() - service_dn.size()) {
+ return service_name.substr(0, needle - 1);
+ } else {
+ return service_name;
+ }
+}
+
+void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
+{
+ if (bytes == 0 || !replyfn) {
+ return;
+ }
+
+ buffer.resize(bytes);
+ const auto dns_msg = DnsMessage::decode(buffer, rq_id);
+ if (dns_msg) {
+ asio::ip::address ip = from.address();
+ if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; }
+ else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; }
+
+ for (const auto &sdpair : dns_msg->sdmap) {
+ if (! sdpair.second.srv) {
+ continue;
+ }
+
+ const auto &srv = *sdpair.second.srv;
+ auto service_name = strip_service_dn(sdpair.first);
+
+ std::string path;
+ std::string version;
+
+ if (sdpair.second.txt) {
+ static const std::string tag_path = "path=";
+ static const std::string tag_version = "version=";
+
+ for (const auto &value : sdpair.second.txt->values) {
+ if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) {
+ path = std::move(value.substr(tag_path.size()));
+ } else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) {
+ version = std::move(value.substr(tag_version.size()));
+ }
+ }
+ }
+
+ BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version));
+ replyfn(std::move(reply));
+ }
+ }
+}
+
+void Bonjour::priv::lookup_perform()
+{
+ const auto brq = BonjourRequest::make(service, protocol);
+ if (!brq) {
+ return;
+ }
+
+ auto self = this;
+ rq_id = brq->id;
+
+ try {
+ boost::asio::io_service io_service;
+ udp::socket socket(io_service);
+ socket.open(udp::v4());
+ socket.set_option(udp::socket::reuse_address(true));
+ udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
+ socket.send_to(asio::buffer(brq->data), mcast);
+
+ bool expired = false;
+ bool retry = false;
+ asio::deadline_timer timer(io_service);
+ retries--;
+ std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
+ if (retries == 0 || error) {
+ expired = true;
+ if (self->completefn) {
+ self->completefn();
+ }
+ } else {
+ retry = true;
+ retries--;
+ timer.expires_from_now(boost::posix_time::seconds(timeout));
+ timer.async_wait(timer_handler);
+ }
+ };
+
+ timer.expires_from_now(boost::posix_time::seconds(timeout));
+ timer.async_wait(timer_handler);
+
+ udp::endpoint recv_from;
+ const auto recv_handler = [&](const error_code &error, size_t bytes) {
+ if (!error) { self->udp_receive(recv_from, bytes); }
+ };
+ socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
+
+ while (io_service.run_one()) {
+ if (expired) {
+ socket.cancel();
+ } else if (retry) {
+ retry = false;
+ socket.send_to(asio::buffer(brq->data), mcast);
+ } else {
+ buffer.resize(DnsMessage::MAX_SIZE);
+ socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
+ }
+ }
+ } catch (std::exception& e) {
+ }
+}
+
+
+// API - public part
+
+BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) :
+ ip(std::move(ip)),
+ port(port),
+ service_name(std::move(service_name)),
+ hostname(std::move(hostname)),
+ path(path.empty() ? std::move(std::string("/")) : std::move(path)),
+ version(version.empty() ? std::move(std::string("Unknown")) : std::move(version))
+{
+ std::string proto;
+ std::string port_suffix;
+ if (port == 443) { proto = "https://"; }
+ if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); }
+ if (this->path[0] != '/') { this->path.insert(0, 1, '/'); }
+ full_address = proto + ip.to_string() + port_suffix;
+ if (this->path != "/") { full_address += path; }
+}
+
+bool BonjourReply::operator==(const BonjourReply &other) const
+{
+ return this->full_address == other.full_address
+ && this->service_name == other.service_name;
+}
+
+bool BonjourReply::operator<(const BonjourReply &other) const
+{
+ if (this->ip != other.ip) {
+ // So that the common case doesn't involve string comparison
+ return this->ip < other.ip;
+ } else {
+ auto cmp = this->full_address.compare(other.full_address);
+ return cmp != 0 ? cmp < 0 : this->service_name < other.service_name;
+ }
+}
+
+std::ostream& operator<<(std::ostream &os, const BonjourReply &reply)
+{
+ os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", "
+ << reply.hostname << ", " << reply.path << ", " << reply.version << ")";
+ return os;
+}
+
+
+Bonjour::Bonjour(std::string service, std::string protocol) :
+ p(new priv(std::move(service), std::move(protocol)))
+{}
+
+Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {}
+
+Bonjour::~Bonjour()
+{
+ if (p && p->io_thread.joinable()) {
+ p->io_thread.detach();
+ }
+}
+
+Bonjour& Bonjour::set_timeout(unsigned timeout)
+{
+ if (p) { p->timeout = timeout; }
+ return *this;
+}
+
+Bonjour& Bonjour::set_retries(unsigned retries)
+{
+ if (p && retries > 0) { p->retries = retries; }
+ return *this;
+}
+
+Bonjour& Bonjour::on_reply(ReplyFn fn)
+{
+ if (p) { p->replyfn = std::move(fn); }
+ return *this;
+}
+
+Bonjour& Bonjour::on_complete(CompleteFn fn)
+{
+ if (p) { p->completefn = std::move(fn); }
+ return *this;
+}
+
+Bonjour::Ptr Bonjour::lookup()
+{
+ auto self = std::make_shared<Bonjour>(std::move(*this));
+
+ if (self->p) {
+ auto io_thread = std::thread([self]() {
+ self->p->lookup_perform();
+ });
+ self->p->io_thread = std::move(io_thread);
+ }
+
+ return self;
+}
+
+
+}
diff --git a/src/slic3r/Utils/Bonjour.hpp b/src/slic3r/Utils/Bonjour.hpp
new file mode 100644
index 000000000..63f34638c
--- /dev/null
+++ b/src/slic3r/Utils/Bonjour.hpp
@@ -0,0 +1,64 @@
+#ifndef slic3r_Bonjour_hpp_
+#define slic3r_Bonjour_hpp_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <functional>
+#include <boost/asio/ip/address.hpp>
+
+
+namespace Slic3r {
+
+
+struct BonjourReply
+{
+ boost::asio::ip::address ip;
+ uint16_t port;
+ std::string service_name;
+ std::string hostname;
+ std::string full_address;
+ std::string path;
+ std::string version;
+
+ BonjourReply() = delete;
+ BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version);
+
+ bool operator==(const BonjourReply &other) const;
+ bool operator<(const BonjourReply &other) const;
+};
+
+std::ostream& operator<<(std::ostream &, const BonjourReply &);
+
+
+/// Bonjour lookup performer
+class Bonjour : public std::enable_shared_from_this<Bonjour> {
+private:
+ struct priv;
+public:
+ typedef std::shared_ptr<Bonjour> Ptr;
+ typedef std::function<void(BonjourReply &&)> ReplyFn;
+ typedef std::function<void()> CompleteFn;
+
+ Bonjour(std::string service, std::string protocol = "tcp");
+ Bonjour(Bonjour &&other);
+ ~Bonjour();
+
+ Bonjour& set_timeout(unsigned timeout);
+ Bonjour& set_retries(unsigned retries);
+ // ^ Note: By default there is 1 retry (meaning 1 broadcast is sent).
+ // Timeout is per one retry, ie. total time spent listening = retries * timeout.
+ // If retries > 1, then care needs to be taken as more than one reply from the same service may be received.
+
+ Bonjour& on_reply(ReplyFn fn);
+ Bonjour& on_complete(CompleteFn fn);
+
+ Ptr lookup();
+private:
+ std::unique_ptr<priv> p;
+};
+
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp
new file mode 100644
index 000000000..f25327161
--- /dev/null
+++ b/src/slic3r/Utils/Duet.cpp
@@ -0,0 +1,279 @@
+#include "Duet.hpp"
+#include "PrintHostSendDialog.hpp"
+
+#include <algorithm>
+#include <ctime>
+#include <boost/filesystem/path.hpp>
+#include <boost/format.hpp>
+#include <boost/log/trivial.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+
+#include <wx/frame.h>
+#include <wx/event.h>
+#include <wx/progdlg.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/checkbox.h>
+
+#include "libslic3r/PrintConfig.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+#include "Http.hpp"
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+namespace Slic3r {
+
+Duet::Duet(DynamicPrintConfig *config) :
+ host(config->opt_string("print_host")),
+ password(config->opt_string("printhost_apikey"))
+{}
+
+Duet::~Duet() {}
+
+bool Duet::test(wxString &msg) const
+{
+ bool connected = connect(msg);
+ if (connected) {
+ disconnect();
+ }
+
+ return connected;
+}
+
+wxString Duet::get_test_ok_msg () const
+{
+ return wxString::Format("%s", _(L("Connection to Duet works correctly.")));
+}
+
+wxString Duet::get_test_failed_msg (wxString &msg) const
+{
+ return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
+}
+
+bool Duet::send_gcode(const std::string &filename) const
+{
+ enum { PROGRESS_RANGE = 1000 };
+
+ const auto errortitle = _(L("Error while uploading to the Duet"));
+ fs::path filepath(filename);
+
+ PrintHostSendDialog send_dialog(filepath.filename(), true);
+ if (send_dialog.ShowModal() != wxID_OK) { return false; }
+
+ const bool print = send_dialog.print();
+ const auto upload_filepath = send_dialog.filename();
+ const auto upload_filename = upload_filepath.filename();
+ const auto upload_parent_path = upload_filepath.parent_path();
+
+ wxProgressDialog progress_dialog(
+ _(L("Duet upload")),
+ _(L("Sending G-code file to Duet...")),
+ PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
+ progress_dialog.Pulse();
+
+ wxString connect_msg;
+ if (!connect(connect_msg)) {
+ auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
+ GUI::show_error(&progress_dialog, std::move(errormsg));
+ return false;
+ }
+
+ bool res = true;
+
+ auto upload_cmd = get_upload_url(upload_filepath.string());
+ BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
+ % filepath.string()
+ % upload_filename.string()
+ % upload_parent_path.string()
+ % print
+ % upload_cmd;
+
+ auto http = Http::post(std::move(upload_cmd));
+ http.set_post_body(filename)
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
+ progress_dialog.Update(PROGRESS_RANGE);
+
+ int err_code = get_err_code_from_body(body);
+ if (err_code != 0) {
+ auto msg = format_error(body, L("Unknown error occured"), 0);
+ GUI::show_error(&progress_dialog, std::move(msg));
+ res = false;
+ } else if (print) {
+ wxString errormsg;
+ res = start_print(errormsg, upload_filepath.string());
+ if (!res) {
+ GUI::show_error(&progress_dialog, std::move(errormsg));
+ }
+ }
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
+ GUI::show_error(&progress_dialog, std::move(errormsg));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool &cancel) {
+ if (cancel) {
+ // Upload was canceled
+ res = false;
+ } else if (progress.ultotal > 0) {
+ int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
+ cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
+ } else {
+ cancel = !progress_dialog.Pulse();
+ }
+ })
+ .perform_sync();
+
+ disconnect();
+
+ return res;
+}
+
+bool Duet::has_auto_discovery() const
+{
+ return false;
+}
+
+bool Duet::can_test() const
+{
+ return true;
+}
+
+bool Duet::connect(wxString &msg) const
+{
+ bool res = false;
+ auto url = get_connect_url();
+
+ auto http = Http::get(std::move(url));
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
+
+ int err_code = get_err_code_from_body(body);
+ switch (err_code) {
+ case 0:
+ res = true;
+ break;
+ case 1:
+ msg = format_error(body, L("Wrong password"), 0);
+ break;
+ case 2:
+ msg = format_error(body, L("Could not get resources to create a new connection"), 0);
+ break;
+ default:
+ msg = format_error(body, L("Unknown error occured"), 0);
+ break;
+ }
+
+ })
+ .perform_sync();
+
+ return res;
+}
+
+void Duet::disconnect() const
+{
+ auto url = (boost::format("%1%rr_disconnect")
+ % get_base_url()).str();
+
+ auto http = Http::get(std::move(url));
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ // we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ })
+ .perform_sync();
+}
+
+std::string Duet::get_upload_url(const std::string &filename) const
+{
+ return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
+ % get_base_url()
+ % Http::url_encode(filename)
+ % timestamp_str()).str();
+}
+
+std::string Duet::get_connect_url() const
+{
+ return (boost::format("%1%rr_connect?password=%2%&%3%")
+ % get_base_url()
+ % (password.empty() ? "reprap" : password)
+ % timestamp_str()).str();
+}
+
+std::string Duet::get_base_url() const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return host;
+ } else {
+ return (boost::format("%1%/") % host).str();
+ }
+ } else {
+ return (boost::format("http://%1%/") % host).str();
+ }
+}
+
+std::string Duet::timestamp_str() const
+{
+ enum { BUFFER_SIZE = 32 };
+
+ auto t = std::time(nullptr);
+ auto tm = *std::localtime(&t);
+
+ char buffer[BUFFER_SIZE];
+ std::strftime(buffer, BUFFER_SIZE, "time=%Y-%m-%dT%H:%M:%S", &tm);
+
+ return std::string(buffer);
+}
+
+wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status)
+{
+ if (status != 0) {
+ auto wxbody = wxString::FromUTF8(body.data());
+ return wxString::Format("HTTP %u: %s", status, wxbody);
+ } else {
+ return wxString::FromUTF8(error.data());
+ }
+}
+
+bool Duet::start_print(wxString &msg, const std::string &filename) const
+{
+ bool res = false;
+
+ auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
+ % get_base_url()
+ % Http::url_encode(filename)).str();
+
+ auto http = Http::get(std::move(url));
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
+ res = true;
+ })
+ .perform_sync();
+
+ return res;
+}
+
+int Duet::get_err_code_from_body(const std::string &body) const
+{
+ pt::ptree root;
+ std::istringstream iss (body); // wrap returned json to istringstream
+ pt::read_json(iss, root);
+
+ return root.get<int>("err", 0);
+}
+
+}
diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp
new file mode 100644
index 000000000..bc210d7a4
--- /dev/null
+++ b/src/slic3r/Utils/Duet.hpp
@@ -0,0 +1,47 @@
+#ifndef slic3r_Duet_hpp_
+#define slic3r_Duet_hpp_
+
+#include <string>
+#include <wx/string.h>
+
+#include "PrintHost.hpp"
+
+
+namespace Slic3r {
+
+
+class DynamicPrintConfig;
+class Http;
+
+class Duet : public PrintHost
+{
+public:
+ Duet(DynamicPrintConfig *config);
+ virtual ~Duet();
+
+ bool test(wxString &curl_msg) const;
+ wxString get_test_ok_msg () const;
+ wxString get_test_failed_msg (wxString &msg) const;
+ // Send gcode file to duet, filename is expected to be in UTF-8
+ bool send_gcode(const std::string &filename) const;
+ bool has_auto_discovery() const;
+ bool can_test() const;
+private:
+ std::string host;
+ std::string password;
+
+ std::string get_upload_url(const std::string &filename) const;
+ std::string get_connect_url() const;
+ std::string get_base_url() const;
+ std::string timestamp_str() const;
+ bool connect(wxString &msg) const;
+ void disconnect() const;
+ bool start_print(wxString &msg, const std::string &filename) const;
+ int get_err_code_from_body(const std::string &body) const;
+ static wxString format_error(const std::string &body, const std::string &error, unsigned status);
+};
+
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp
new file mode 100644
index 000000000..556035a5b
--- /dev/null
+++ b/src/slic3r/Utils/FixModelByWin10.cpp
@@ -0,0 +1,402 @@
+#ifdef HAS_WIN10SDK
+
+#ifndef NOMINMAX
+# define NOMINMAX
+#endif
+
+#include "FixModelByWin10.hpp"
+
+#include <atomic>
+#include <chrono>
+#include <cstdint>
+#include <condition_variable>
+#include <exception>
+#include <string>
+#include <thread>
+
+#include <boost/filesystem.hpp>
+#include <boost/nowide/convert.hpp>
+#include <boost/nowide/cstdio.hpp>
+
+#include <roapi.h>
+// for ComPtr
+#include <wrl/client.h>
+// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/
+#include <winrt/robuffer.h>
+#include <winrt/windows.storage.provider.h>
+#include <winrt/windows.graphics.printing3d.h>
+
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Print.hpp"
+#include "libslic3r/Format/3mf.hpp"
+#include "../GUI/GUI.hpp"
+#include "../GUI/PresetBundle.hpp"
+
+#include <wx/msgdlg.h>
+#include <wx/progdlg.h>
+
+extern "C"{
+ // from rapi.h
+ typedef HRESULT (__stdcall* FunctionRoInitialize)(int);
+ typedef HRESULT (__stdcall* FunctionRoUninitialize)();
+ typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance);
+ typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory);
+ // from winstring.h
+ typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string);
+ typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string);
+}
+
+namespace Slic3r {
+
+HMODULE s_hRuntimeObjectLibrary = nullptr;
+FunctionRoInitialize s_RoInitialize = nullptr;
+FunctionRoUninitialize s_RoUninitialize = nullptr;
+FunctionRoActivateInstance s_RoActivateInstance = nullptr;
+FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr;
+FunctionWindowsCreateString s_WindowsCreateString = nullptr;
+FunctionWindowsDelteString s_WindowsDeleteString = nullptr;
+
+bool winrt_load_runtime_object_library()
+{
+ if (s_hRuntimeObjectLibrary == nullptr)
+ s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll");
+ if (s_hRuntimeObjectLibrary != nullptr) {
+ s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize");
+ s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize");
+ s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance");
+ s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory");
+ s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString");
+ s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString");
+ }
+ return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString;
+}
+
+static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst)
+{
+ HSTRING hClassName;
+ HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
+ if (S_OK != hr)
+ return hr;
+ hr = (*s_RoActivateInstance)(hClassName, pinst);
+ (*s_WindowsDeleteString)(hClassName);
+ return hr;
+}
+
+template<typename TYPE>
+static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst)
+{
+ IInspectable *pinspectable = nullptr;
+ HRESULT hr = winrt_activate_instance(class_name, &pinspectable);
+ if (S_OK != hr)
+ return hr;
+ hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst);
+ pinspectable->Release();
+ return hr;
+}
+
+static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst)
+{
+ HSTRING hClassName;
+ HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName);
+ if (S_OK != hr)
+ return hr;
+ hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst);
+ (*s_WindowsDeleteString)(hClassName);
+ return hr;
+}
+
+template<typename TYPE>
+static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst)
+{
+ return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast<void**>(pinst));
+}
+
+// To be called often to test whether to cancel the operation.
+typedef std::function<void ()> ThrowOnCancelFn;
+
+template<typename T>
+static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr<T> &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100)
+{
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+ asyncAction.As(&asyncInfo);
+ AsyncStatus status;
+ // Ugly blocking loop until the RepairAsync call finishes.
+//FIXME replace with a callback.
+// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage
+ for (;;) {
+ asyncInfo->get_Status(&status);
+ if (status != AsyncStatus::Started)
+ return status;
+ throw_on_cancel();
+ ::Sleep(blocking_tick_ms);
+ }
+}
+
+static HRESULT winrt_open_file_stream(
+ const std::wstring &path,
+ ABI::Windows::Storage::FileAccessMode mode,
+ ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream,
+ ThrowOnCancelFn throw_on_cancel)
+{
+ // Get the file factory.
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFileStatics> fileFactory;
+ HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf());
+ if (FAILED(hr)) return hr;
+
+ // Open the file asynchronously.
+ HSTRING hstr_path;
+ hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path);
+ if (FAILED(hr)) return hr;
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::StorageFile*>> fileOpenAsync;
+ hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf());
+ if (FAILED(hr)) return hr;
+ (*s_WindowsDeleteString)(hstr_path);
+
+ // Wait until the file gets open, get the actual file.
+ AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel);
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::IStorageFile> storageFile;
+ if (status == AsyncStatus::Completed) {
+ hr = fileOpenAsync->GetResults(storageFile.GetAddressOf());
+ } else {
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+ hr = fileOpenAsync.As(&asyncInfo);
+ if (FAILED(hr)) return hr;
+ HRESULT err;
+ hr = asyncInfo->get_ErrorCode(&err);
+ return FAILED(hr) ? hr : err;
+ }
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> fileStreamAsync;
+ hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf());
+ if (FAILED(hr)) return hr;
+
+ status = winrt_async_await(fileStreamAsync, throw_on_cancel);
+ if (status == AsyncStatus::Completed) {
+ hr = fileStreamAsync->GetResults(fileStream);
+ } else {
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> asyncInfo;
+ hr = fileStreamAsync.As(&asyncInfo);
+ if (FAILED(hr)) return hr;
+ HRESULT err;
+ hr = asyncInfo->get_ErrorCode(&err);
+ if (!FAILED(hr))
+ hr = err;
+ }
+ return hr;
+}
+
+bool is_windows10()
+{
+ HKEY hKey;
+ LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
+ if (lRes == ERROR_SUCCESS) {
+ WCHAR szBuffer[512];
+ DWORD dwBufferSize = sizeof(szBuffer);
+ lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize);
+ if (lRes == ERROR_SUCCESS)
+ return wcsncmp(szBuffer, L"Windows 10", 10) == 0;
+ RegCloseKey(hKey);
+ }
+ return false;
+}
+
+// Progress function, to be called regularly to update the progress.
+typedef std::function<void (const char * /* message */, unsigned /* progress */)> ProgressFn;
+
+void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel)
+{
+ if (! is_windows10())
+ throw std::runtime_error("fix_model_by_win10_sdk called on non Windows 10 system");
+
+ if (! winrt_load_runtime_object_library())
+ throw std::runtime_error("Failed to initialize the WinRT library.");
+
+ HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
+ {
+ on_progress(L("Exporting the source model"), 20);
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> fileStream;
+ hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3D3MFPackage> printing3d3mfpackage;
+ hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf());
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Graphics::Printing3D::Printing3DModel*>> modelAsync;
+ hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf());
+
+ AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel);
+ Microsoft::WRL::ComPtr<ABI::Windows::Graphics::Printing3D::IPrinting3DModel> model;
+ if (status == AsyncStatus::Completed)
+ hr = modelAsync->GetResults(model.GetAddressOf());
+ else
+ throw std::runtime_error(L("Failed loading the input model."));
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::Graphics::Printing3D::Printing3DMesh*>> meshes;
+ hr = model->get_Meshes(meshes.GetAddressOf());
+ unsigned num_meshes = 0;
+ hr = meshes->get_Size(&num_meshes);
+
+ on_progress(L("Repairing the model by the Netfabb service"), 40);
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> repairAsync;
+ hr = model->RepairAsync(repairAsync.GetAddressOf());
+ status = winrt_async_await(repairAsync, throw_on_cancel);
+ if (status != AsyncStatus::Completed)
+ throw std::runtime_error(L("Mesh repair failed."));
+ repairAsync->GetResults();
+
+ on_progress(L("Loading the repaired model"), 60);
+
+ // Verify the number of meshes returned after the repair action.
+ meshes.Reset();
+ hr = model->get_Meshes(meshes.GetAddressOf());
+ hr = meshes->get_Size(&num_meshes);
+
+ // Save model to this class' Printing3D3MFPackage.
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> saveToPackageAsync;
+ hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf());
+ status = winrt_async_await(saveToPackageAsync, throw_on_cancel);
+ if (status != AsyncStatus::Completed)
+ throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
+ hr = saveToPackageAsync->GetResults();
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::Storage::Streams::IRandomAccessStream*>> generatorStreamAsync;
+ hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf());
+ status = winrt_async_await(generatorStreamAsync, throw_on_cancel);
+ if (status != AsyncStatus::Completed)
+ throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> generatorStream;
+ hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf());
+
+ // Go to the beginning of the stream.
+ generatorStream->Seek(0);
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IInputStream> inputStream;
+ hr = generatorStream.As(&inputStream);
+
+ // Get the buffer factory.
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBufferFactory> bufferFactory;
+ hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf());
+
+ // Open the destination file.
+ FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
+ byte *buffer_ptr;
+ bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
+ {
+ Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
+ buffer.As(&bufferByteAccess);
+ hr = bufferByteAccess->Buffer(&buffer_ptr);
+ }
+ uint32_t length;
+ hr = buffer->get_Length(&length);
+
+ Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
+ for (;;) {
+ hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
+ status = winrt_async_await(asyncRead, throw_on_cancel);
+ if (status != AsyncStatus::Completed)
+ throw std::runtime_error(L("Saving mesh into the 3MF container failed."));
+ hr = buffer->get_Length(&length);
+ if (length == 0)
+ break;
+ fwrite(buffer_ptr, length, 1, fout);
+ }
+ fclose(fout);
+ // Here all the COM objects will be released through the ComPtr destructors.
+ }
+ (*s_RoUninitialize)();
+}
+
+class RepairCanceledException : public std::exception {
+public:
+ const char* what() const throw() { return "Model repair has been canceled"; }
+};
+
+void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result)
+{
+ std::mutex mutex;
+ std::condition_variable condition;
+ std::unique_lock<std::mutex> lock(mutex);
+ struct Progress {
+ std::string message;
+ int percent = 0;
+ bool updated = false;
+ } progress;
+ std::atomic<bool> canceled = false;
+ std::atomic<bool> finished = false;
+
+ // Open a progress dialog.
+ wxProgressDialog progress_dialog(
+ _(L("Model fixing")),
+ _(L("Exporting model...")),
+ 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
+ // Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
+ // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
+ bool success = false;
+ auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) {
+ std::lock_guard<std::mutex> lk(mutex);
+ progress.message = msg;
+ progress.percent = prcnt;
+ progress.updated = true;
+ condition.notify_all();
+ };
+ auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() {
+ try {
+ on_progress(L("Exporting the source model"), 0);
+ boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+ path_src += ".3mf";
+ Model model;
+ model.add_object(model_object);
+ if (! Slic3r::store_3mf(path_src.string().c_str(), &model, const_cast<Print*>(&print), false)) {
+ boost::filesystem::remove(path_src);
+ throw std::runtime_error(L("Export of a temporary 3mf file failed"));
+ }
+ model.clear_objects();
+ model.clear_materials();
+ boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+ path_dst += ".3mf";
+ fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,
+ [&canceled]() { if (canceled) throw RepairCanceledException(); });
+ boost::filesystem::remove(path_src);
+ PresetBundle bundle;
+ on_progress(L("Loading the repaired model"), 80);
+ bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &bundle, &result);
+ boost::filesystem::remove(path_dst);
+ if (! loaded)
+ throw std::runtime_error(L("Import of the repaired 3mf file failed"));
+ success = true;
+ finished = true;
+ on_progress(L("Model repair finished"), 100);
+ } catch (RepairCanceledException &ex) {
+ canceled = true;
+ finished = true;
+ on_progress(L("Model repair canceled"), 100);
+ } catch (std::exception &ex) {
+ success = false;
+ finished = true;
+ on_progress(ex.what(), 100);
+ }
+ });
+ while (! finished) {
+ condition.wait_for(lock, std::chrono::milliseconds(500), [&progress]{ return progress.updated; });
+ if (! progress_dialog.Update(progress.percent, _(progress.message)))
+ canceled = true;
+ progress.updated = false;
+ }
+
+ if (canceled) {
+ // Nothing to show.
+ } else if (success) {
+ wxMessageDialog dlg(nullptr, _(L("Model repaired successfully")), _(L("Model Repair by the Netfabb service")), wxICON_INFORMATION | wxOK_DEFAULT);
+ dlg.ShowModal();
+ } else {
+ wxMessageDialog dlg(nullptr, _(L("Model repair failed: \n")) + _(progress.message), _(L("Model Repair by the Netfabb service")), wxICON_ERROR | wxOK_DEFAULT);
+ dlg.ShowModal();
+ }
+ worker_thread.join();
+}
+
+} // namespace Slic3r
+
+#endif /* HAS_WIN10SDK */
diff --git a/src/slic3r/Utils/FixModelByWin10.hpp b/src/slic3r/Utils/FixModelByWin10.hpp
new file mode 100644
index 000000000..c148a6970
--- /dev/null
+++ b/src/slic3r/Utils/FixModelByWin10.hpp
@@ -0,0 +1,26 @@
+#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_
+#define slic3r_GUI_Utils_FixModelByWin10_hpp_
+
+#include <string>
+
+namespace Slic3r {
+
+class Model;
+class ModelObject;
+class Print;
+
+#ifdef HAS_WIN10SDK
+
+extern bool is_windows10();
+extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result);
+
+#else /* HAS_WIN10SDK */
+
+inline bool is_windows10() { return false; }
+inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {}
+
+#endif /* HAS_WIN10SDK */
+
+} // namespace Slic3r
+
+#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */
diff --git a/src/slic3r/Utils/HexFile.cpp b/src/slic3r/Utils/HexFile.cpp
new file mode 100644
index 000000000..282c647bd
--- /dev/null
+++ b/src/slic3r/Utils/HexFile.cpp
@@ -0,0 +1,106 @@
+#include "HexFile.hpp"
+
+#include <sstream>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ini_parser.hpp>
+
+namespace fs = boost::filesystem;
+namespace pt = boost::property_tree;
+
+
+namespace Slic3r {
+namespace Utils {
+
+
+static HexFile::DeviceKind parse_device_kind(const std::string &str)
+{
+ if (str == "mk2") { return HexFile::DEV_MK2; }
+ else if (str == "mk3") { return HexFile::DEV_MK3; }
+ else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
+ else { return HexFile::DEV_GENERIC; }
+}
+
+static size_t hex_num_sections(fs::ifstream &file)
+{
+ file.seekg(0);
+ if (! file.good()) {
+ return 0;
+ }
+
+ static const char *hex_terminator = ":00000001FF\r";
+ size_t res = 0;
+ std::string line;
+ while (getline(file, line, '\n').good()) {
+ // Account for LF vs CRLF
+ if (!line.empty() && line.back() != '\r') {
+ line.push_back('\r');
+ }
+
+ if (line == hex_terminator) {
+ res++;
+ }
+ }
+
+ return res;
+}
+
+HexFile::HexFile(fs::path path) :
+ path(std::move(path))
+{
+ fs::ifstream file(this->path);
+ if (! file.good()) {
+ return;
+ }
+
+ std::string line;
+ std::stringstream header_ini;
+ while (std::getline(file, line, '\n').good()) {
+ if (line.empty()) {
+ continue;
+ }
+
+ // Account for LF vs CRLF
+ if (!line.empty() && line.back() == '\r') {
+ line.pop_back();
+ }
+
+ if (line.front() == ';') {
+ line.front() = ' ';
+ header_ini << line << std::endl;
+ } else if (line.front() == ':') {
+ break;
+ }
+ }
+
+ pt::ptree ptree;
+ try {
+ pt::read_ini(header_ini, ptree);
+ } catch (std::exception &e) {
+ return;
+ }
+
+ bool has_device_meta = false;
+ const auto device = ptree.find("device");
+ if (device != ptree.not_found()) {
+ this->device = parse_device_kind(device->second.data());
+ has_device_meta = true;
+ }
+
+ const auto model_id = ptree.find("model_id");
+ if (model_id != ptree.not_found()) {
+ this->model_id = model_id->second.data();
+ }
+
+ if (! has_device_meta) {
+ // No device metadata, look at the number of 'sections'
+ if (hex_num_sections(file) == 2) {
+ // Looks like a pre-metadata l10n firmware for the MK3, assume that's the case
+ this->device = DEV_MK3;
+ }
+ }
+}
+
+
+}
+}
diff --git a/src/slic3r/Utils/HexFile.hpp b/src/slic3r/Utils/HexFile.hpp
new file mode 100644
index 000000000..1201d23a4
--- /dev/null
+++ b/src/slic3r/Utils/HexFile.hpp
@@ -0,0 +1,33 @@
+#ifndef slic3r_Hex_hpp_
+#define slic3r_Hex_hpp_
+
+#include <string>
+#include <boost/filesystem/path.hpp>
+
+
+namespace Slic3r {
+namespace Utils {
+
+
+struct HexFile
+{
+ enum DeviceKind {
+ DEV_GENERIC,
+ DEV_MK2,
+ DEV_MK3,
+ DEV_MM_CONTROL,
+ };
+
+ boost::filesystem::path path;
+ DeviceKind device = DEV_GENERIC;
+ std::string model_id;
+
+ HexFile() {}
+ HexFile(boost::filesystem::path path);
+};
+
+
+}
+}
+
+#endif
diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp
new file mode 100644
index 000000000..9b67ceea8
--- /dev/null
+++ b/src/slic3r/Utils/Http.cpp
@@ -0,0 +1,451 @@
+#include "Http.hpp"
+
+#include <cstdlib>
+#include <functional>
+#include <thread>
+#include <deque>
+#include <sstream>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/format.hpp>
+
+#include <curl/curl.h>
+
+#include "../../libslic3r/libslic3r.h"
+
+namespace fs = boost::filesystem;
+
+
+namespace Slic3r {
+
+
+// Private
+
+class CurlGlobalInit
+{
+ static const CurlGlobalInit instance;
+
+ CurlGlobalInit() { ::curl_global_init(CURL_GLOBAL_DEFAULT); }
+ ~CurlGlobalInit() { ::curl_global_cleanup(); }
+};
+
+struct Http::priv
+{
+ enum {
+ DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
+ };
+
+ ::CURL *curl;
+ ::curl_httppost *form;
+ ::curl_httppost *form_end;
+ ::curl_slist *headerlist;
+ // Used for reading the body
+ std::string buffer;
+ // Used for storing file streams added as multipart form parts
+ // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
+ std::deque<fs::ifstream> form_files;
+ std::string postfields;
+ size_t limit;
+ bool cancel;
+
+ std::thread io_thread;
+ Http::CompleteFn completefn;
+ Http::ErrorFn errorfn;
+ Http::ProgressFn progressfn;
+
+ priv(const std::string &url);
+ ~priv();
+
+ static bool ca_file_supported(::CURL *curl);
+ static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
+ static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
+ static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow);
+ static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
+
+ void form_add_file(const char *name, const fs::path &path, const char* filename);
+ void set_post_body(const fs::path &path);
+
+ std::string curl_error(CURLcode curlcode);
+ std::string body_size_error();
+ void http_perform();
+};
+
+Http::priv::priv(const std::string &url) :
+ curl(::curl_easy_init()),
+ form(nullptr),
+ form_end(nullptr),
+ headerlist(nullptr),
+ limit(0),
+ cancel(false)
+{
+ if (curl == nullptr) {
+ throw std::runtime_error(std::string("Could not construct Curl object"));
+ }
+
+ ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
+ ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION);
+}
+
+Http::priv::~priv()
+{
+ ::curl_easy_cleanup(curl);
+ ::curl_formfree(form);
+ ::curl_slist_free_all(headerlist);
+}
+
+bool Http::priv::ca_file_supported(::CURL *curl)
+{
+#ifdef _WIN32
+ bool res = false;
+#else
+ bool res = true;
+#endif
+
+ if (curl == nullptr) { return res; }
+
+#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
+ ::curl_tlssessioninfo *tls;
+ if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
+ if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
+ // With Windows and OS X native SSL support, cert files cannot be set
+ res = false;
+ }
+ }
+#endif
+
+ return res;
+}
+
+size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
+{
+ auto self = static_cast<priv*>(userp);
+ const char *cdata = static_cast<char*>(data);
+ const size_t realsize = size * nmemb;
+
+ const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
+ if (self->buffer.size() + realsize > limit) {
+ // This makes curl_easy_perform return CURLE_WRITE_ERROR
+ return 0;
+ }
+
+ self->buffer.append(cdata, realsize);
+
+ return realsize;
+}
+
+int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
+{
+ auto self = static_cast<priv*>(userp);
+ bool cb_cancel = false;
+
+ if (self->progressfn) {
+ Progress progress(dltotal, dlnow, ultotal, ulnow);
+ self->progressfn(progress, cb_cancel);
+ }
+
+ return self->cancel || cb_cancel;
+}
+
+int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow)
+{
+ return xfercb(userp, dltotal, dlnow, ultotal, ulnow);
+}
+
+size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp)
+{
+ auto stream = reinterpret_cast<fs::ifstream*>(userp);
+
+ try {
+ stream->read(buffer, size * nitems);
+ } catch (...) {
+ return CURL_READFUNC_ABORT;
+ }
+
+ return stream->gcount();
+}
+
+void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
+{
+ // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
+ // and so we use CURLFORM_STREAM with boost ifstream to read the file.
+
+ if (filename == nullptr) {
+ filename = path.string().c_str();
+ }
+
+ form_files.emplace_back(path, std::ios::in | std::ios::binary);
+ auto &stream = form_files.back();
+ stream.seekg(0, std::ios::end);
+ size_t size = stream.tellg();
+ stream.seekg(0);
+
+ if (filename != nullptr) {
+ ::curl_formadd(&form, &form_end,
+ CURLFORM_COPYNAME, name,
+ CURLFORM_FILENAME, filename,
+ CURLFORM_CONTENTTYPE, "application/octet-stream",
+ CURLFORM_STREAM, static_cast<void*>(&stream),
+ CURLFORM_CONTENTSLENGTH, static_cast<long>(size),
+ CURLFORM_END
+ );
+ }
+}
+
+void Http::priv::set_post_body(const fs::path &path)
+{
+ std::ifstream file(path.string());
+ std::string file_content { std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
+ postfields = file_content;
+}
+
+std::string Http::priv::curl_error(CURLcode curlcode)
+{
+ return (boost::format("%1% (%2%)")
+ % ::curl_easy_strerror(curlcode)
+ % curlcode
+ ).str();
+}
+
+std::string Http::priv::body_size_error()
+{
+ return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
+}
+
+void Http::priv::http_perform()
+{
+ ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
+ ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
+ ::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb);
+
+ ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
+#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
+ ::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb);
+ ::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast<void*>(this));
+ (void)xfercb_legacy; // prevent unused function warning
+#else
+ ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb);
+ ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
+#endif
+
+#ifndef NDEBUG
+ ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+#endif
+
+ if (headerlist != nullptr) {
+ ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
+ }
+
+ if (form != nullptr) {
+ ::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
+ }
+
+ if (!postfields.empty()) {
+ ::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
+ ::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
+ }
+
+ CURLcode res = ::curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ if (res == CURLE_ABORTED_BY_CALLBACK) {
+ if (cancel) {
+ // The abort comes from the request being cancelled programatically
+ Progress dummyprogress(0, 0, 0, 0);
+ bool cancel = true;
+ if (progressfn) { progressfn(dummyprogress, cancel); }
+ } else {
+ // The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed
+ if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); }
+ }
+ }
+ else if (res == CURLE_WRITE_ERROR) {
+ if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); }
+ } else {
+ if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); }
+ };
+ } else {
+ long http_status = 0;
+ ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
+
+ if (http_status >= 400) {
+ if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
+ } else {
+ if (completefn) { completefn(std::move(buffer), http_status); }
+ }
+ }
+}
+
+Http::Http(const std::string &url) : p(new priv(url)) {}
+
+
+// Public
+
+Http::Http(Http &&other) : p(std::move(other.p)) {}
+
+Http::~Http()
+{
+ if (p && p->io_thread.joinable()) {
+ p->io_thread.detach();
+ }
+}
+
+
+Http& Http::size_limit(size_t sizeLimit)
+{
+ if (p) { p->limit = sizeLimit; }
+ return *this;
+}
+
+Http& Http::header(std::string name, const std::string &value)
+{
+ if (!p) { return * this; }
+
+ if (name.size() > 0) {
+ name.append(": ").append(value);
+ } else {
+ name.push_back(':');
+ }
+ p->headerlist = curl_slist_append(p->headerlist, name.c_str());
+ return *this;
+}
+
+Http& Http::remove_header(std::string name)
+{
+ if (p) {
+ name.push_back(':');
+ p->headerlist = curl_slist_append(p->headerlist, name.c_str());
+ }
+
+ return *this;
+}
+
+Http& Http::ca_file(const std::string &name)
+{
+ if (p && priv::ca_file_supported(p->curl)) {
+ ::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
+ }
+
+ return *this;
+}
+
+Http& Http::form_add(const std::string &name, const std::string &contents)
+{
+ if (p) {
+ ::curl_formadd(&p->form, &p->form_end,
+ CURLFORM_COPYNAME, name.c_str(),
+ CURLFORM_COPYCONTENTS, contents.c_str(),
+ CURLFORM_END
+ );
+ }
+
+ return *this;
+}
+
+Http& Http::form_add_file(const std::string &name, const fs::path &path)
+{
+ if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); }
+ return *this;
+}
+
+Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename)
+{
+ if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); }
+ return *this;
+}
+
+Http& Http::set_post_body(const fs::path &path)
+{
+ if (p) { p->set_post_body(path);}
+ return *this;
+}
+
+Http& Http::on_complete(CompleteFn fn)
+{
+ if (p) { p->completefn = std::move(fn); }
+ return *this;
+}
+
+Http& Http::on_error(ErrorFn fn)
+{
+ if (p) { p->errorfn = std::move(fn); }
+ return *this;
+}
+
+Http& Http::on_progress(ProgressFn fn)
+{
+ if (p) { p->progressfn = std::move(fn); }
+ return *this;
+}
+
+Http::Ptr Http::perform()
+{
+ auto self = std::make_shared<Http>(std::move(*this));
+
+ if (self->p) {
+ auto io_thread = std::thread([self](){
+ self->p->http_perform();
+ });
+ self->p->io_thread = std::move(io_thread);
+ }
+
+ return self;
+}
+
+void Http::perform_sync()
+{
+ if (p) { p->http_perform(); }
+}
+
+void Http::cancel()
+{
+ if (p) { p->cancel = true; }
+}
+
+Http Http::get(std::string url)
+{
+ return std::move(Http{std::move(url)});
+}
+
+Http Http::post(std::string url)
+{
+ Http http{std::move(url)};
+ curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
+ return http;
+}
+
+bool Http::ca_file_supported()
+{
+ ::CURL *curl = ::curl_easy_init();
+ bool res = priv::ca_file_supported(curl);
+ if (curl != nullptr) { ::curl_easy_cleanup(curl); }
+ return res;
+}
+
+std::string Http::url_encode(const std::string &str)
+{
+ ::CURL *curl = ::curl_easy_init();
+ if (curl == nullptr) {
+ return str;
+ }
+ char *ce = ::curl_easy_escape(curl, str.c_str(), str.length());
+ std::string encoded = std::string(ce);
+
+ ::curl_free(ce);
+ ::curl_easy_cleanup(curl);
+
+ return encoded;
+}
+
+std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
+{
+ os << "Http::Progress("
+ << "dltotal = " << progress.dltotal
+ << ", dlnow = " << progress.dlnow
+ << ", ultotal = " << progress.ultotal
+ << ", ulnow = " << progress.ulnow
+ << ")";
+ return os;
+}
+
+
+}
diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp
new file mode 100644
index 000000000..44580b7ea
--- /dev/null
+++ b/src/slic3r/Utils/Http.hpp
@@ -0,0 +1,115 @@
+#ifndef slic3r_Http_hpp_
+#define slic3r_Http_hpp_
+
+#include <memory>
+#include <string>
+#include <functional>
+#include <boost/filesystem/path.hpp>
+
+
+namespace Slic3r {
+
+
+/// Represetns a Http request
+class Http : public std::enable_shared_from_this<Http> {
+private:
+ struct priv;
+public:
+ struct Progress
+ {
+ size_t dltotal; // Total bytes to download
+ size_t dlnow; // Bytes downloaded so far
+ size_t ultotal; // Total bytes to upload
+ size_t ulnow; // Bytes uploaded so far
+
+ Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) :
+ dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow)
+ {}
+ };
+
+ typedef std::shared_ptr<Http> Ptr;
+ typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
+
+ // A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
+ // If the HTTP request could not be made or failed before completion, the `error` arg contains a description
+ // of the error and `http_status` is zero.
+ // If the HTTP request was completed but the response HTTP code is >= 400, `error` is empty and `http_status` contains the response code.
+ // In either case there may or may not be a body.
+ typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
+
+ // See the Progress struct above.
+ // Writing true to the `cancel` reference cancels the request in progress.
+ typedef std::function<void(Progress, bool& /* cancel */)> ProgressFn;
+
+ Http(Http &&other);
+
+ // Note: strings are expected to be UTF-8-encoded
+
+ // These are the primary constructors that create a HTTP object
+ // for a GET and a POST request respectively.
+ static Http get(std::string url);
+ static Http post(std::string url);
+ ~Http();
+
+ Http(const Http &) = delete;
+ Http& operator=(const Http &) = delete;
+ Http& operator=(Http &&) = delete;
+
+ // Sets a maximum size of the data that can be received.
+ // A value of zero sets the default limit, which is is 5MB.
+ Http& size_limit(size_t sizeLimit);
+ // Sets a HTTP header field.
+ Http& header(std::string name, const std::string &value);
+ // Removes a header field.
+ Http& remove_header(std::string name);
+ // Sets a CA certificate file for usage with HTTPS. This is only supported on some backends,
+ // specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store.
+ // See also ca_file_supported().
+ Http& ca_file(const std::string &filename);
+ // Add a HTTP multipart form field
+ Http& form_add(const std::string &name, const std::string &contents);
+ // Add a HTTP multipart form file data contents, `name` is the name of the part
+ Http& form_add_file(const std::string &name, const boost::filesystem::path &path);
+ // Same as above except also override the file's filename with a custom one
+ Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
+
+ // Set the file contents as a POST request body.
+ // The data is used verbatim, it is not additionally encoded in any way.
+ // This can be used for hosts which do not support multipart requests.
+ Http& set_post_body(const boost::filesystem::path &path);
+
+ // Callback called on HTTP request complete
+ Http& on_complete(CompleteFn fn);
+ // Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup,
+ // TCP connection, HTTP transfer, and finally also when the response indicates an error (status >= 400).
+ // Therefore, a response body may or may not be present.
+ Http& on_error(ErrorFn fn);
+ // Callback called on data download/upload prorgess (called fairly frequently).
+ // See the `Progress` structure for description of the data passed.
+ // Writing a true-ish value into the cancel reference parameter cancels the request.
+ Http& on_progress(ProgressFn fn);
+
+ // Starts performing the request in a background thread
+ Ptr perform();
+ // Starts performing the request on the current thread
+ void perform_sync();
+ // Cancels a request in progress
+ void cancel();
+
+ // Tells whether current backend supports seting up a CA file using ca_file()
+ static bool ca_file_supported();
+
+ // converts the given string to an url_encoded_string
+ static std::string url_encode(const std::string &str);
+private:
+ Http(const std::string &url);
+
+ std::unique_ptr<priv> p;
+};
+
+std::ostream& operator<<(std::ostream &, const Http::Progress &);
+
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp
new file mode 100644
index 000000000..db86d7697
--- /dev/null
+++ b/src/slic3r/Utils/OctoPrint.cpp
@@ -0,0 +1,173 @@
+#include "OctoPrint.hpp"
+#include "PrintHostSendDialog.hpp"
+
+#include <algorithm>
+#include <boost/format.hpp>
+#include <boost/log/trivial.hpp>
+
+#include "libslic3r/PrintConfig.hpp"
+#include "Http.hpp"
+
+namespace fs = boost::filesystem;
+
+
+namespace Slic3r {
+
+OctoPrint::OctoPrint(DynamicPrintConfig *config) :
+ host(config->opt_string("print_host")),
+ apikey(config->opt_string("printhost_apikey")),
+ cafile(config->opt_string("printhost_cafile"))
+{}
+
+OctoPrint::~OctoPrint() {}
+
+bool OctoPrint::test(wxString &msg) const
+{
+ // Since the request is performed synchronously here,
+ // it is ok to refer to `msg` from within the closure
+
+ bool res = true;
+ auto url = make_url("api/version");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url;
+
+ auto http = Http::get(std::move(url));
+ set_auth(http);
+ http.on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ res = false;
+ msg = format_error(body, error, status);
+ })
+ .on_complete([&](std::string body, unsigned) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body;
+ })
+ .perform_sync();
+
+ return res;
+}
+
+wxString OctoPrint::get_test_ok_msg () const
+{
+ return wxString::Format("%s", _(L("Connection to OctoPrint works correctly.")));
+}
+
+wxString OctoPrint::get_test_failed_msg (wxString &msg) const
+{
+ return wxString::Format("%s: %s\n\n%s",
+ _(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
+}
+
+bool OctoPrint::send_gcode(const std::string &filename) const
+{
+ enum { PROGRESS_RANGE = 1000 };
+
+ const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
+ fs::path filepath(filename);
+
+ PrintHostSendDialog send_dialog(filepath.filename(), true);
+ if (send_dialog.ShowModal() != wxID_OK) { return false; }
+
+ const bool print = send_dialog.print();
+ const auto upload_filepath = send_dialog.filename();
+ const auto upload_filename = upload_filepath.filename();
+ const auto upload_parent_path = upload_filepath.parent_path();
+
+ wxProgressDialog progress_dialog(
+ _(L("OctoPrint upload")),
+ _(L("Sending G-code file to the OctoPrint server...")),
+ PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
+ progress_dialog.Pulse();
+
+ wxString test_msg;
+ if (!test(test_msg)) {
+ auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
+ GUI::show_error(&progress_dialog, std::move(errormsg));
+ return false;
+ }
+
+ bool res = true;
+
+ auto url = make_url("api/files/local");
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%")
+ % filepath.string()
+ % url
+ % upload_filename.string()
+ % upload_parent_path.string()
+ % print;
+
+ auto http = Http::post(std::move(url));
+ set_auth(http);
+ http.form_add("print", print ? "true" : "false")
+ .form_add("path", upload_parent_path.string())
+ .form_add_file("file", filename, upload_filename.string())
+ .on_complete([&](std::string body, unsigned status) {
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
+ progress_dialog.Update(PROGRESS_RANGE);
+ })
+ .on_error([&](std::string body, std::string error, unsigned status) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
+ auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
+ GUI::show_error(&progress_dialog, std::move(errormsg));
+ res = false;
+ })
+ .on_progress([&](Http::Progress progress, bool &cancel) {
+ if (cancel) {
+ // Upload was canceled
+ res = false;
+ } else if (progress.ultotal > 0) {
+ int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
+ cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
+ } else {
+ cancel = !progress_dialog.Pulse();
+ }
+ })
+ .perform_sync();
+
+ return res;
+}
+
+bool OctoPrint::has_auto_discovery() const
+{
+ return true;
+}
+
+bool OctoPrint::can_test() const
+{
+ return true;
+}
+
+void OctoPrint::set_auth(Http &http) const
+{
+ http.header("X-Api-Key", apikey);
+
+ if (! cafile.empty()) {
+ http.ca_file(cafile);
+ }
+}
+
+std::string OctoPrint::make_url(const std::string &path) const
+{
+ if (host.find("http://") == 0 || host.find("https://") == 0) {
+ if (host.back() == '/') {
+ return (boost::format("%1%%2%") % host % path).str();
+ } else {
+ return (boost::format("%1%/%2%") % host % path).str();
+ }
+ } else {
+ return (boost::format("http://%1%/%2%") % host % path).str();
+ }
+}
+
+wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status)
+{
+ if (status != 0) {
+ auto wxbody = wxString::FromUTF8(body.data());
+ return wxString::Format("HTTP %u: %s", status, wxbody);
+ } else {
+ return wxString::FromUTF8(error.data());
+ }
+}
+
+
+}
diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp
new file mode 100644
index 000000000..f6c4d58c8
--- /dev/null
+++ b/src/slic3r/Utils/OctoPrint.hpp
@@ -0,0 +1,42 @@
+#ifndef slic3r_OctoPrint_hpp_
+#define slic3r_OctoPrint_hpp_
+
+#include <string>
+#include <wx/string.h>
+
+#include "PrintHost.hpp"
+
+
+namespace Slic3r {
+
+
+class DynamicPrintConfig;
+class Http;
+
+class OctoPrint : public PrintHost
+{
+public:
+ OctoPrint(DynamicPrintConfig *config);
+ virtual ~OctoPrint();
+
+ bool test(wxString &curl_msg) const;
+ wxString get_test_ok_msg () const;
+ wxString get_test_failed_msg (wxString &msg) const;
+ // Send gcode file to octoprint, filename is expected to be in UTF-8
+ bool send_gcode(const std::string &filename) const;
+ bool has_auto_discovery() const;
+ bool can_test() const;
+private:
+ std::string host;
+ std::string apikey;
+ std::string cafile;
+
+ void set_auth(Http &http) const;
+ std::string make_url(const std::string &path) const;
+ static wxString format_error(const std::string &body, const std::string &error, unsigned status);
+};
+
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp
new file mode 100644
index 000000000..2e423dc5e
--- /dev/null
+++ b/src/slic3r/Utils/PresetUpdater.cpp
@@ -0,0 +1,633 @@
+#include "PresetUpdater.hpp"
+
+#include <algorithm>
+#include <thread>
+#include <unordered_map>
+#include <ostream>
+#include <stdexcept>
+#include <boost/format.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/log/trivial.hpp>
+
+#include <wx/app.h>
+#include <wx/event.h>
+#include <wx/msgdlg.h>
+
+#include "libslic3r/libslic3r.h"
+#include "libslic3r/Utils.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/PresetBundle.hpp"
+#include "slic3r/GUI/UpdateDialogs.hpp"
+#include "slic3r/GUI/ConfigWizard.hpp"
+#include "slic3r/Utils/Http.hpp"
+#include "slic3r/Config/Version.hpp"
+#include "slic3r/Config/Snapshot.hpp"
+
+namespace fs = boost::filesystem;
+using Slic3r::GUI::Config::Index;
+using Slic3r::GUI::Config::Version;
+using Slic3r::GUI::Config::Snapshot;
+using Slic3r::GUI::Config::SnapshotDB;
+
+
+namespace Slic3r {
+
+
+enum {
+ SLIC3R_VERSION_BODY_MAX = 256,
+};
+
+static const char *INDEX_FILENAME = "index.idx";
+static const char *TMP_EXTENSION = ".download";
+
+
+struct Update
+{
+ fs::path source;
+ fs::path target;
+ Version version;
+
+ Update(fs::path &&source, fs::path &&target, const Version &version) :
+ source(std::move(source)),
+ target(std::move(target)),
+ version(version)
+ {}
+
+ std::string name() const { return source.stem().string(); }
+
+ friend std::ostream& operator<<(std::ostream& os , const Update &self) {
+ os << "Update(" << self.source.string() << " -> " << self.target.string() << ')';
+ return os;
+ }
+};
+
+struct Incompat
+{
+ fs::path bundle;
+ Version version;
+
+ Incompat(fs::path &&bundle, const Version &version) :
+ bundle(std::move(bundle)),
+ version(version)
+ {}
+
+ std::string name() const { return bundle.stem().string(); }
+
+ friend std::ostream& operator<<(std::ostream& os , const Incompat &self) {
+ os << "Incompat(" << self.bundle.string() << ')';
+ return os;
+ }
+};
+
+struct Updates
+{
+ std::vector<Incompat> incompats;
+ std::vector<Update> updates;
+};
+
+
+struct PresetUpdater::priv
+{
+ int version_online_event;
+ std::vector<Index> index_db;
+
+ bool enabled_version_check;
+ bool enabled_config_update;
+ std::string version_check_url;
+ bool had_config_update;
+
+ fs::path cache_path;
+ fs::path rsrc_path;
+ fs::path vendor_path;
+
+ bool cancel;
+ std::thread thread;
+
+ priv(int version_online_event);
+
+ void set_download_prefs(AppConfig *app_config);
+ bool get_file(const std::string &url, const fs::path &target_path) const;
+ void prune_tmps() const;
+ void sync_version() const;
+ void sync_config(const std::set<VendorProfile> vendors) const;
+
+ void check_install_indices() const;
+ Updates get_config_updates() const;
+ void perform_updates(Updates &&updates, bool snapshot = true) const;
+
+ static void copy_file(const fs::path &from, const fs::path &to);
+};
+
+PresetUpdater::priv::priv(int version_online_event) :
+ version_online_event(version_online_event),
+ had_config_update(false),
+ cache_path(fs::path(Slic3r::data_dir()) / "cache"),
+ rsrc_path(fs::path(resources_dir()) / "profiles"),
+ vendor_path(fs::path(Slic3r::data_dir()) / "vendor"),
+ cancel(false)
+{
+ set_download_prefs(GUI::get_app_config());
+ check_install_indices();
+ index_db = std::move(Index::load_db());
+}
+
+// Pull relevant preferences from AppConfig
+void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
+{
+ enabled_version_check = app_config->get("version_check") == "1";
+ version_check_url = app_config->version_check_url();
+ enabled_config_update = app_config->get("preset_update") == "1" && !app_config->legacy_datadir();
+}
+
+// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
+bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const
+{
+ bool res = false;
+ fs::path tmp_path = target_path;
+ tmp_path += (boost::format(".%1%%2%") % get_current_pid() % TMP_EXTENSION).str();
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`")
+ % url
+ % target_path.string()
+ % tmp_path.string();
+
+ Http::get(url)
+ .on_progress([this](Http::Progress, bool &cancel) {
+ if (cancel) { cancel = true; }
+ })
+ .on_error([&](std::string body, std::string error, unsigned http_status) {
+ (void)body;
+ BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
+ % url
+ % http_status
+ % error;
+ })
+ .on_complete([&](std::string body, unsigned /* http_status */) {
+ fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
+ file.write(body.c_str(), body.size());
+ file.close();
+ fs::rename(tmp_path, target_path);
+ res = true;
+ })
+ .perform_sync();
+
+ return res;
+}
+
+// Remove leftover paritally downloaded files, if any.
+void PresetUpdater::priv::prune_tmps() const
+{
+ for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) {
+ if (it->path().extension() == TMP_EXTENSION) {
+ BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string();
+ fs::remove(it->path());
+ }
+ }
+}
+
+// Get Slic3rPE version available online, save in AppConfig.
+void PresetUpdater::priv::sync_version() const
+{
+ if (! enabled_version_check) { return; }
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Downloading Slic3rPE online version from: `%1%`") % version_check_url;
+
+ Http::get(version_check_url)
+ .size_limit(SLIC3R_VERSION_BODY_MAX)
+ .on_progress([this](Http::Progress, bool &cancel) {
+ cancel = this->cancel;
+ })
+ .on_error([&](std::string body, std::string error, unsigned http_status) {
+ (void)body;
+ BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
+ % version_check_url
+ % http_status
+ % error;
+ })
+ .on_complete([&](std::string body, unsigned /* http_status */) {
+ boost::trim(body);
+ BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body;
+ wxCommandEvent* evt = new wxCommandEvent(version_online_event);
+ evt->SetString(body);
+ GUI::get_app()->QueueEvent(evt);
+ })
+ .perform_sync();
+}
+
+// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
+// Both are saved in cache.
+void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors) const
+{
+ BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache";
+
+ if (!enabled_config_update) { return; }
+
+ // Donwload vendor preset bundles
+ for (const auto &index : index_db) {
+ if (cancel) { return; }
+
+ const auto vendor_it = vendors.find(VendorProfile(index.vendor()));
+ if (vendor_it == vendors.end()) {
+ BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor();
+ continue;
+ }
+
+ const VendorProfile &vendor = *vendor_it;
+ if (vendor.config_update_url.empty()) {
+ BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name;
+ continue;
+ }
+
+ // Download a fresh index
+ BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name;
+ const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME;
+ const auto idx_path = cache_path / (vendor.id + ".idx");
+ if (! get_file(idx_url, idx_path)) { continue; }
+ if (cancel) { return; }
+
+ // Load the fresh index up
+ Index new_index;
+ new_index.load(idx_path);
+
+ // See if a there's a new version to download
+ const auto recommended_it = new_index.recommended();
+ if (recommended_it == new_index.end()) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name;
+ continue;
+ }
+ const auto recommended = recommended_it->config_version;
+
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Got index for vendor: %1%: current version: %2%, recommended version: %3%")
+ % vendor.name
+ % vendor.config_version.to_string()
+ % recommended.to_string();
+
+ if (vendor.config_version >= recommended) { continue; }
+
+ // Download a fresh bundle
+ BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name;
+ const auto bundle_url = (boost::format("%1%/%2%.ini") % vendor.config_update_url % recommended.to_string()).str();
+ const auto bundle_path = cache_path / (vendor.id + ".ini");
+ if (! get_file(bundle_url, bundle_path)) { continue; }
+ if (cancel) { return; }
+ }
+}
+
+// Install indicies from resources. Only installs those that are either missing or older than in resources.
+void PresetUpdater::priv::check_install_indices() const
+{
+ BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources...";
+
+ for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) {
+ const auto &path = it->path();
+ if (path.extension() == ".idx") {
+ const auto path_in_cache = cache_path / path.filename();
+
+ if (! fs::exists(path_in_cache)) {
+ BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename();
+ copy_file(path, path_in_cache);
+ } else {
+ Index idx_rsrc, idx_cache;
+ idx_rsrc.load(path);
+ idx_cache.load(path_in_cache);
+
+ if (idx_cache.version() < idx_rsrc.version()) {
+ BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename();
+ copy_file(path, path_in_cache);
+ }
+ }
+ }
+ }
+}
+
+// Generates a list of bundle updates that are to be performed
+Updates PresetUpdater::priv::get_config_updates() const
+{
+ Updates updates;
+
+ BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates...";
+
+ for (const auto idx : index_db) {
+ auto bundle_path = vendor_path / (idx.vendor() + ".ini");
+
+ if (! fs::exists(bundle_path)) {
+ BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
+ continue;
+ }
+
+ // Perform a basic load and check the version
+ const auto vp = VendorProfile::from_ini(bundle_path, false);
+
+ const auto ver_current = idx.find(vp.config_version);
+ if (ver_current == idx.end()) {
+ auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str();
+ BOOST_LOG_TRIVIAL(error) << message;
+ throw std::runtime_error(message);
+ }
+
+ // Getting a recommended version from the latest index, wich may have been downloaded
+ // from the internet, or installed / updated from the installation resources.
+ const auto recommended = idx.recommended();
+ if (recommended == idx.end()) {
+ BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%")
+ % vp.name
+ % ver_current->config_version.to_string()
+ % recommended->config_version.to_string();
+
+ if (! ver_current->is_current_slic3r_supported()) {
+ BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
+ updates.incompats.emplace_back(std::move(bundle_path), *ver_current);
+ } else if (recommended->config_version > ver_current->config_version) {
+ // Config bundle update situation
+
+ // Check if the update is already present in a snapshot
+ const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version);
+ if (recommended_snap != SnapshotDB::singleton().end()) {
+ BOOST_LOG_TRIVIAL(info) << boost::format("Bundle update %1% %2% already found in snapshot %3%, skipping...")
+ % vp.name
+ % recommended->config_version.to_string()
+ % recommended_snap->id;
+ continue;
+ }
+
+ auto path_src = cache_path / (idx.vendor() + ".ini");
+ auto path_in_rsrc = rsrc_path / (idx.vendor() + ".ini");
+ if (! fs::exists(path_src)) {
+ if (! fs::exists(path_in_rsrc)) {
+ BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update, but bundle found in neither cache nor resources")
+ % idx.vendor();
+ continue;
+ } else {
+ path_src = std::move(path_in_rsrc);
+ path_in_rsrc.clear();
+ }
+ }
+
+ auto new_vp = VendorProfile::from_ini(path_src, false);
+ bool found = false;
+ if (new_vp.config_version == recommended->config_version) {
+ updates.updates.emplace_back(std::move(path_src), std::move(bundle_path), *recommended);
+ found = true;
+ } else if (! path_in_rsrc.empty() && fs::exists(path_in_rsrc)) {
+ new_vp = VendorProfile::from_ini(path_in_rsrc, false);
+ if (new_vp.config_version == recommended->config_version) {
+ updates.updates.emplace_back(std::move(path_in_rsrc), std::move(bundle_path), *recommended);
+ found = true;
+ }
+ }
+ if (! found)
+ BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources")
+ % idx.vendor()
+ % recommended->config_version.to_string();
+ }
+ }
+
+ return updates;
+}
+
+void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
+{
+ if (updates.incompats.size() > 0) {
+ if (snapshot) {
+ BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
+ SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_DOWNGRADE);
+ }
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size();
+
+ for (const auto &incompat : updates.incompats) {
+ BOOST_LOG_TRIVIAL(info) << '\t' << incompat;
+ fs::remove(incompat.bundle);
+ }
+ }
+ else if (updates.updates.size() > 0) {
+ if (snapshot) {
+ BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
+ SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE);
+ }
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.updates.size();
+
+ for (const auto &update : updates.updates) {
+ BOOST_LOG_TRIVIAL(info) << '\t' << update;
+
+ copy_file(update.source, update.target);
+
+ PresetBundle bundle;
+ bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets")
+ % (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
+
+ auto preset_remover = [](const Preset &preset) {
+ BOOST_LOG_TRIVIAL(info) << '\t' << preset.file;
+ fs::remove(preset.file);
+ };
+
+ for (const auto &preset : bundle.prints) { preset_remover(preset); }
+ for (const auto &preset : bundle.filaments) { preset_remover(preset); }
+ for (const auto &preset : bundle.printers) { preset_remover(preset); }
+
+ // Also apply the `obsolete_presets` property, removing obsolete ini files
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% obsolete presets")
+ % (bundle.obsolete_presets.prints.size() + bundle.obsolete_presets.filaments.size() + bundle.obsolete_presets.printers.size());
+
+ auto obsolete_remover = [](const char *subdir, const std::string &preset) {
+ auto path = fs::path(Slic3r::data_dir()) / subdir / preset;
+ path += ".ini";
+ BOOST_LOG_TRIVIAL(info) << '\t' << path.string();
+ fs::remove(path);
+ };
+
+ for (const auto &name : bundle.obsolete_presets.prints) { obsolete_remover("print", name); }
+ for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); }
+ for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("sla_material", name); }
+ for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
+ }
+ }
+}
+
+void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &target)
+{
+ static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
+
+ // Make sure the file has correct permission both before and after we copy over it
+ if (fs::exists(target)) {
+ fs::permissions(target, perms);
+ }
+ fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
+ fs::permissions(target, perms);
+}
+
+
+PresetUpdater::PresetUpdater(int version_online_event) :
+ p(new priv(version_online_event))
+{}
+
+
+// Public
+
+PresetUpdater::~PresetUpdater()
+{
+ if (p && p->thread.joinable()) {
+ // This will stop transfers being done by the thread, if any.
+ // Cancelling takes some time, but should complete soon enough.
+ p->cancel = true;
+ p->thread.join();
+ }
+}
+
+void PresetUpdater::sync(PresetBundle *preset_bundle)
+{
+ p->set_download_prefs(GUI::get_app_config());
+ if (!p->enabled_version_check && !p->enabled_config_update) { return; }
+
+ // Copy the whole vendors data for use in the background thread
+ // Unfortunatelly as of C++11, it needs to be copied again
+ // into the closure (but perhaps the compiler can elide this).
+ std::set<VendorProfile> vendors = preset_bundle->vendors;
+
+ p->thread = std::move(std::thread([this, vendors]() {
+ this->p->prune_tmps();
+ this->p->sync_version();
+ this->p->sync_config(std::move(vendors));
+ }));
+}
+
+void PresetUpdater::slic3r_update_notify()
+{
+ if (! p->enabled_version_check) { return; }
+
+ if (p->had_config_update) {
+ BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed";
+ return;
+ }
+
+ auto* app_config = GUI::get_app_config();
+ const auto ver_slic3r = Semver::parse(SLIC3R_VERSION);
+ const auto ver_online_str = app_config->get("version_online");
+ const auto ver_online = Semver::parse(ver_online_str);
+ const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen"));
+ if (! ver_slic3r) {
+ throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION);
+ }
+
+ if (ver_online) {
+ // Only display the notification if the version available online is newer AND if we haven't seen it before
+ if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) {
+ GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online);
+ notification.ShowModal();
+ if (notification.disable_version_check()) {
+ app_config->set("version_check", "0");
+ p->enabled_version_check = false;
+ }
+ }
+ app_config->set("version_online_seen", ver_online_str);
+ }
+}
+
+bool PresetUpdater::config_update() const
+{
+ if (! p->enabled_config_update) { return true; }
+
+ auto updates = p->get_config_updates();
+ if (updates.incompats.size() > 0) {
+ BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size();
+
+ std::unordered_map<std::string, wxString> incompats_map;
+ for (const auto &incompat : updates.incompats) {
+ auto vendor = incompat.name();
+
+ const auto min_slic3r = incompat.version.min_slic3r_version;
+ const auto max_slic3r = incompat.version.max_slic3r_version;
+ wxString restrictions;
+ if (min_slic3r != Semver::zero() && max_slic3r != Semver::inf()) {
+ restrictions = wxString::Format(_(L("requires min. %s and max. %s")),
+ min_slic3r.to_string(),
+ max_slic3r.to_string()
+ );
+ } else if (min_slic3r != Semver::zero()) {
+ restrictions = wxString::Format(_(L("requires min. %s")), min_slic3r.to_string());
+ } else {
+ restrictions = wxString::Format(_(L("requires max. %s")), max_slic3r.to_string());
+ }
+
+ incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions)));
+ }
+
+ p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl
+
+ GUI::MsgDataIncompatible dlg(std::move(incompats_map));
+ const auto res = dlg.ShowModal();
+ if (res == wxID_REPLACE) {
+ BOOST_LOG_TRIVIAL(info) << "User wants to re-configure...";
+ p->perform_updates(std::move(updates));
+ GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT);
+ if (! wizard.run(GUI::get_preset_bundle(), this)) {
+ return false;
+ }
+ GUI::load_current_presets();
+ } else {
+ BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye...";
+ return false;
+ }
+ }
+ else if (updates.updates.size() > 0) {
+ BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.updates.size();
+
+ std::unordered_map<std::string, std::string> updates_map;
+ for (const auto &update : updates.updates) {
+ auto vendor = update.name();
+ auto ver_str = update.version.config_version.to_string();
+ if (! update.version.comment.empty()) {
+ ver_str += std::string(" (") + update.version.comment + ")";
+ }
+ updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str)));
+ }
+
+ p->had_config_update = true; // Ditto, see above
+
+ GUI::MsgUpdateConfig dlg(std::move(updates_map));
+
+ const auto res = dlg.ShowModal();
+ if (res == wxID_OK) {
+ BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
+ p->perform_updates(std::move(updates));
+
+ // Reload global configuration
+ auto *app_config = GUI::get_app_config();
+ GUI::get_preset_bundle()->load_presets(*app_config);
+ GUI::load_current_presets();
+ } else {
+ BOOST_LOG_TRIVIAL(info) << "User refused the update";
+ }
+ } else {
+ BOOST_LOG_TRIVIAL(info) << "No configuration updates available.";
+ }
+
+ return true;
+}
+
+void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
+{
+ Updates updates;
+
+ BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size();
+
+ for (const auto &bundle : bundles) {
+ auto path_in_rsrc = p->rsrc_path / bundle;
+ auto path_in_vendors = p->vendor_path / bundle;
+ updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version());
+ }
+
+ p->perform_updates(std::move(updates), snapshot);
+}
+
+
+}
diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp
new file mode 100644
index 000000000..6a53cca81
--- /dev/null
+++ b/src/slic3r/Utils/PresetUpdater.hpp
@@ -0,0 +1,42 @@
+#ifndef slic3r_PresetUpdate_hpp_
+#define slic3r_PresetUpdate_hpp_
+
+#include <memory>
+#include <vector>
+
+namespace Slic3r {
+
+
+class AppConfig;
+class PresetBundle;
+
+class PresetUpdater
+{
+public:
+ PresetUpdater(int version_online_event);
+ PresetUpdater(PresetUpdater &&) = delete;
+ PresetUpdater(const PresetUpdater &) = delete;
+ PresetUpdater &operator=(PresetUpdater &&) = delete;
+ PresetUpdater &operator=(const PresetUpdater &) = delete;
+ ~PresetUpdater();
+
+ // If either version check or config updating is enabled, get the appropriate data in the background and cache it.
+ void sync(PresetBundle *preset_bundle);
+
+ // If version check is enabled, check if chaced online slic3r version is newer, notify if so.
+ void slic3r_update_notify();
+
+ // If updating is enabled, check if updates are available in cache, if so, ask about installation.
+ // A false return value implies Slic3r should exit due to incompatibility of configuration.
+ bool config_update() const;
+
+ // "Update" a list of bundles from resources (behaves like an online update).
+ void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
+private:
+ struct priv;
+ std::unique_ptr<priv> p;
+};
+
+
+}
+#endif
diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp
new file mode 100644
index 000000000..dd72bae40
--- /dev/null
+++ b/src/slic3r/Utils/PrintHost.cpp
@@ -0,0 +1,23 @@
+#include "OctoPrint.hpp"
+#include "Duet.hpp"
+
+#include "libslic3r/PrintConfig.hpp"
+
+namespace Slic3r {
+
+
+PrintHost::~PrintHost() {}
+
+PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
+{
+ PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
+ if (kind == htOctoPrint) {
+ return new OctoPrint(config);
+ } else if (kind == htDuet) {
+ return new Duet(config);
+ }
+ return nullptr;
+}
+
+
+}
diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp
new file mode 100644
index 000000000..bc828ea46
--- /dev/null
+++ b/src/slic3r/Utils/PrintHost.hpp
@@ -0,0 +1,35 @@
+#ifndef slic3r_PrintHost_hpp_
+#define slic3r_PrintHost_hpp_
+
+#include <memory>
+#include <string>
+#include <wx/string.h>
+
+
+namespace Slic3r {
+
+
+class DynamicPrintConfig;
+
+class PrintHost
+{
+public:
+ virtual ~PrintHost();
+
+ virtual bool test(wxString &curl_msg) const = 0;
+ virtual wxString get_test_ok_msg () const = 0;
+ virtual wxString get_test_failed_msg (wxString &msg) const = 0;
+ // Send gcode file to print host, filename is expected to be in UTF-8
+ virtual bool send_gcode(const std::string &filename) const = 0;
+ virtual bool has_auto_discovery() const = 0;
+ virtual bool can_test() const = 0;
+
+ static PrintHost* get_print_host(DynamicPrintConfig *config);
+};
+
+
+
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/PrintHostSendDialog.cpp b/src/slic3r/Utils/PrintHostSendDialog.cpp
new file mode 100644
index 000000000..c5d441f87
--- /dev/null
+++ b/src/slic3r/Utils/PrintHostSendDialog.cpp
@@ -0,0 +1,52 @@
+#include "PrintHostSendDialog.hpp"
+
+#include <wx/frame.h>
+#include <wx/event.h>
+#include <wx/progdlg.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/checkbox.h>
+
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+
+
+namespace fs = boost::filesystem;
+
+namespace Slic3r {
+
+PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) :
+ MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE),
+ txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
+ box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))),
+ can_start_print(can_start_print)
+{
+ auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
+ label_dir_hint->Wrap(CONTENT_WIDTH);
+
+ content_sizer->Add(txt_filename, 0, wxEXPAND);
+ content_sizer->Add(label_dir_hint);
+ content_sizer->AddSpacer(VERT_SPACING);
+ content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
+
+ btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
+
+ txt_filename->SetFocus();
+ wxString stem(path.stem().wstring());
+ txt_filename->SetSelection(0, stem.Length());
+
+ box_print->Enable(can_start_print);
+
+ Fit();
+}
+
+fs::path PrintHostSendDialog::filename() const
+{
+ return fs::path(txt_filename->GetValue().wx_str());
+}
+
+bool PrintHostSendDialog::print() const
+{
+ return box_print->GetValue(); }
+}
diff --git a/src/slic3r/Utils/PrintHostSendDialog.hpp b/src/slic3r/Utils/PrintHostSendDialog.hpp
new file mode 100644
index 000000000..dc4a8d6f7
--- /dev/null
+++ b/src/slic3r/Utils/PrintHostSendDialog.hpp
@@ -0,0 +1,38 @@
+#ifndef slic3r_PrintHostSendDialog_hpp_
+#define slic3r_PrintHostSendDialog_hpp_
+
+#include <string>
+
+#include <boost/filesystem/path.hpp>
+
+#include <wx/string.h>
+#include <wx/frame.h>
+#include <wx/event.h>
+#include <wx/progdlg.h>
+#include <wx/sizer.h>
+#include <wx/stattext.h>
+#include <wx/textctrl.h>
+#include <wx/checkbox.h>
+
+#include "slic3r/GUI/GUI.hpp"
+#include "slic3r/GUI/MsgDialog.hpp"
+
+
+namespace Slic3r {
+
+class PrintHostSendDialog : public GUI::MsgDialog
+{
+private:
+ wxTextCtrl *txt_filename;
+ wxCheckBox *box_print;
+ bool can_start_print;
+
+public:
+ PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
+ boost::filesystem::path filename() const;
+ bool print() const;
+};
+
+}
+
+#endif
diff --git a/src/slic3r/Utils/Semver.hpp b/src/slic3r/Utils/Semver.hpp
new file mode 100644
index 000000000..736f9b891
--- /dev/null
+++ b/src/slic3r/Utils/Semver.hpp
@@ -0,0 +1,151 @@
+#ifndef slic3r_Semver_hpp_
+#define slic3r_Semver_hpp_
+
+#include <string>
+#include <cstring>
+#include <ostream>
+#include <stdexcept>
+#include <boost/optional.hpp>
+#include <boost/format.hpp>
+
+#include "semver/semver.h"
+
+namespace Slic3r {
+
+
+class Semver
+{
+public:
+ struct Major { const int i; Major(int i) : i(i) {} };
+ struct Minor { const int i; Minor(int i) : i(i) {} };
+ struct Patch { const int i; Patch(int i) : i(i) {} };
+
+ Semver() : ver(semver_zero()) {}
+
+ Semver(int major, int minor, int patch,
+ boost::optional<const std::string&> metadata = boost::none,
+ boost::optional<const std::string&> prerelease = boost::none)
+ : ver(semver_zero())
+ {
+ ver.major = major;
+ ver.minor = minor;
+ ver.patch = patch;
+ set_metadata(metadata);
+ set_prerelease(prerelease);
+ }
+
+ Semver(const std::string &str) : ver(semver_zero())
+ {
+ auto parsed = parse(str);
+ if (! parsed) {
+ throw std::runtime_error(std::string("Could not parse version string: ") + str);
+ }
+ ver = parsed->ver;
+ parsed->ver = semver_zero();
+ }
+
+ static boost::optional<Semver> parse(const std::string &str)
+ {
+ semver_t ver = semver_zero();
+ if (::semver_parse(str.c_str(), &ver) == 0) {
+ return Semver(ver);
+ } else {
+ return boost::none;
+ }
+ }
+
+ static const Semver zero() { return Semver(semver_zero()); }
+
+ static const Semver inf()
+ {
+ static semver_t ver = { std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), nullptr, nullptr };
+ return Semver(ver);
+ }
+
+ static const Semver invalid()
+ {
+ static semver_t ver = { -1, 0, 0, nullptr, nullptr };
+ return Semver(ver);
+ }
+
+ Semver(Semver &&other) : ver(other.ver) { other.ver = semver_zero(); }
+ Semver(const Semver &other) : ver(::semver_copy(&other.ver)) {}
+
+ Semver &operator=(Semver &&other)
+ {
+ ::semver_free(&ver);
+ ver = other.ver;
+ other.ver = semver_zero();
+ return *this;
+ }
+
+ Semver &operator=(const Semver &other)
+ {
+ ::semver_free(&ver);
+ ver = ::semver_copy(&other.ver);
+ return *this;
+ }
+
+ ~Semver() { ::semver_free(&ver); }
+
+ // const accessors
+ int maj() const { return ver.major; }
+ int min() const { return ver.minor; }
+ int patch() const { return ver.patch; }
+ const char* prerelease() const { return ver.prerelease; }
+ const char* metadata() const { return ver.metadata; }
+
+ // Setters
+ void set_maj(int maj) { ver.major = maj; }
+ void set_min(int min) { ver.minor = min; }
+ void set_patch(int patch) { ver.patch = patch; }
+ void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
+ void set_prerelease(boost::optional<const std::string&> pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; }
+
+ // Comparison
+ bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; }
+ bool operator<=(const Semver &b) const { return ::semver_compare(ver, b.ver) <= 0; }
+ bool operator==(const Semver &b) const { return ::semver_compare(ver, b.ver) == 0; }
+ bool operator!=(const Semver &b) const { return ::semver_compare(ver, b.ver) != 0; }
+ bool operator>=(const Semver &b) const { return ::semver_compare(ver, b.ver) >= 0; }
+ bool operator>(const Semver &b) const { return ::semver_compare(ver, b.ver) == 1; }
+ // We're using '&' instead of the '~' operator here as '~' is unary-only:
+ // Satisfies patch if Major and minor are equal.
+ bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver); }
+ bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver); }
+ bool in_range(const Semver &low, const Semver &high) const { return low <= *this && *this <= high; }
+
+ // Conversion
+ std::string to_string() const {
+ auto res = (boost::format("%1%.%2%.%3%") % ver.major % ver.minor % ver.patch).str();
+ if (ver.prerelease != nullptr) { res += '-'; res += ver.prerelease; }
+ if (ver.metadata != nullptr) { res += '+'; res += ver.metadata; }
+ return res;
+ }
+
+ // Arithmetics
+ Semver& operator+=(const Major &b) { ver.major += b.i; return *this; }
+ Semver& operator+=(const Minor &b) { ver.minor += b.i; return *this; }
+ Semver& operator+=(const Patch &b) { ver.patch += b.i; return *this; }
+ Semver& operator-=(const Major &b) { ver.major -= b.i; return *this; }
+ Semver& operator-=(const Minor &b) { ver.minor -= b.i; return *this; }
+ Semver& operator-=(const Patch &b) { ver.patch -= b.i; return *this; }
+ Semver operator+(const Major &b) const { Semver res(*this); return res += b; }
+ Semver operator+(const Minor &b) const { Semver res(*this); return res += b; }
+ Semver operator+(const Patch &b) const { Semver res(*this); return res += b; }
+ Semver operator-(const Major &b) const { Semver res(*this); return res -= b; }
+ Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; }
+ Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; }
+
+private:
+ semver_t ver;
+
+ Semver(semver_t ver) : ver(ver) {}
+
+ static semver_t semver_zero() { return { 0, 0, 0, nullptr, nullptr }; }
+ static char * strdup(const std::string &str) { return ::semver_strdup(const_cast<char*>(str.c_str())); }
+};
+
+
+}
+#endif
diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp
new file mode 100644
index 000000000..601719b50
--- /dev/null
+++ b/src/slic3r/Utils/Serial.cpp
@@ -0,0 +1,495 @@
+#include "Serial.hpp"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <chrono>
+#include <thread>
+#include <fstream>
+#include <stdexcept>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/format.hpp>
+#include <boost/optional.hpp>
+
+#if _WIN32
+ #include <Windows.h>
+ #include <Setupapi.h>
+ #include <initguid.h>
+ #include <devguid.h>
+ #include <regex>
+ // Undefine min/max macros incompatible with the standard library
+ // For example, std::numeric_limits<std::streamsize>::max()
+ // produces some weird errors
+ #ifdef min
+ #undef min
+ #endif
+ #ifdef max
+ #undef max
+ #endif
+ #include "boost/nowide/convert.hpp"
+ #pragma comment(lib, "user32.lib")
+#elif __APPLE__
+ #include <CoreFoundation/CoreFoundation.h>
+ #include <CoreFoundation/CFString.h>
+ #include <IOKit/IOKitLib.h>
+ #include <IOKit/serial/IOSerialKeys.h>
+ #include <IOKit/serial/ioss.h>
+ #include <sys/syslimits.h>
+#endif
+
+#ifndef _WIN32
+ #include <sys/ioctl.h>
+ #include <sys/time.h>
+ #include <sys/unistd.h>
+ #include <sys/select.h>
+#endif
+
+#if defined(__APPLE__) || defined(__OpenBSD__)
+ #include <termios.h>
+#elif defined __linux__
+ #include <fcntl.h>
+ #include <asm-generic/ioctls.h>
+#endif
+
+using boost::optional;
+
+
+namespace Slic3r {
+namespace Utils {
+
+static bool looks_like_printer(const std::string &friendly_name)
+{
+ return friendly_name.find("Original Prusa") != std::string::npos;
+}
+
+#if _WIN32
+void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi)
+{
+ unsigned vid, pid;
+ std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*");
+ std::smatch matches;
+ if (std::regex_match(hardware_id, matches, pattern)) {
+ try {
+ vid = std::stoul(matches[1].str(), 0, 16);
+ pid = std::stoul(matches[2].str(), 0, 16);
+ spi.id_vendor = vid;
+ spi.id_product = pid;
+ }
+ catch (...) {}
+ }
+}
+#endif
+
+#ifdef __linux__
+optional<std::string> sysfs_tty_prop(const std::string &tty_dev, const std::string &name)
+{
+ const auto prop_path = (boost::format("/sys/class/tty/%1%/device/../%2%") % tty_dev % name).str();
+ std::ifstream file(prop_path);
+ std::string res;
+
+ std::getline(file, res);
+ if (file.good()) { return res; }
+ else { return boost::none; }
+}
+
+optional<unsigned long> sysfs_tty_prop_hex(const std::string &tty_dev, const std::string &name)
+{
+ auto prop = sysfs_tty_prop(tty_dev, name);
+ if (!prop) { return boost::none; }
+
+ try { return std::stoul(*prop, 0, 16); }
+ catch (...) { return boost::none; }
+}
+#endif
+
+std::vector<SerialPortInfo> scan_serial_ports_extended()
+{
+ std::vector<SerialPortInfo> output;
+
+#ifdef _WIN32
+ SP_DEVINFO_DATA devInfoData = { 0 };
+ devInfoData.cbSize = sizeof(devInfoData);
+ // Get the tree containing the info for the ports.
+ HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0, nullptr, DIGCF_PRESENT);
+ if (hDeviceInfo != INVALID_HANDLE_VALUE) {
+ // Iterate over all the devices in the tree.
+ for (int nDevice = 0; SetupDiEnumDeviceInfo(hDeviceInfo, nDevice, &devInfoData); ++ nDevice) {
+ SerialPortInfo port_info;
+ // Get the registry key which stores the ports settings.
+ HKEY hDeviceKey = SetupDiOpenDevRegKey(hDeviceInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
+ if (hDeviceKey) {
+ // Read in the name of the port.
+ wchar_t pszPortName[4096];
+ DWORD dwSize = sizeof(pszPortName);
+ DWORD dwType = 0;
+ if (RegQueryValueEx(hDeviceKey, L"PortName", NULL, &dwType, (LPBYTE)pszPortName, &dwSize) == ERROR_SUCCESS)
+ port_info.port = boost::nowide::narrow(pszPortName);
+ RegCloseKey(hDeviceKey);
+ if (port_info.port.empty())
+ continue;
+ }
+
+ // Find the size required to hold the device info.
+ DWORD regDataType;
+ DWORD reqSize = 0;
+ SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
+ std::vector<wchar_t> hardware_id(reqSize > 1 ? reqSize : 1);
+ // Now store it in a buffer.
+ if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, (BYTE*)hardware_id.data(), reqSize, nullptr))
+ continue;
+ parse_hardware_id(boost::nowide::narrow(hardware_id.data()), port_info);
+
+ // Find the size required to hold the friendly name.
+ reqSize = 0;
+ SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
+ std::vector<wchar_t> friendly_name;
+ friendly_name.reserve(reqSize > 1 ? reqSize : 1);
+ // Now store it in a buffer.
+ if (! SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, (BYTE*)friendly_name.data(), reqSize, nullptr)) {
+ port_info.friendly_name = port_info.port;
+ } else {
+ port_info.friendly_name = boost::nowide::narrow(friendly_name.data());
+ port_info.is_printer = looks_like_printer(port_info.friendly_name);
+ }
+ output.emplace_back(std::move(port_info));
+ }
+ }
+#elif __APPLE__
+ // inspired by https://sigrok.org/wiki/Libserialport
+ CFMutableDictionaryRef classes = IOServiceMatching(kIOSerialBSDServiceValue);
+ if (classes != 0) {
+ io_iterator_t iter;
+ if (IOServiceGetMatchingServices(kIOMasterPortDefault, classes, &iter) == KERN_SUCCESS) {
+ io_object_t port;
+ while ((port = IOIteratorNext(iter)) != 0) {
+ CFTypeRef cf_property = IORegistryEntryCreateCFProperty(port, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
+ if (cf_property) {
+ char path[PATH_MAX];
+ Boolean result = CFStringGetCString((CFStringRef)cf_property, path, sizeof(path), kCFStringEncodingUTF8);
+ CFRelease(cf_property);
+ if (result) {
+ SerialPortInfo port_info;
+ port_info.port = path;
+
+ // Attempt to read out the device friendly name
+ if ((cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
+ CFSTR("USB Interface Name"), kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
+ (cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
+ CFSTR("USB Product Name"), kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
+ (cf_property = IORegistryEntrySearchCFProperty(port, kIOServicePlane,
+ CFSTR("Product Name"), kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents)) ||
+ (cf_property = IORegistryEntryCreateCFProperty(port,
+ CFSTR(kIOTTYDeviceKey), kCFAllocatorDefault, 0))) {
+ // Description limited to 127 char, anything longer would not be user friendly anyway.
+ char description[128];
+ if (CFStringGetCString((CFStringRef)cf_property, description, sizeof(description), kCFStringEncodingUTF8)) {
+ port_info.friendly_name = std::string(description) + " (" + port_info.port + ")";
+ port_info.is_printer = looks_like_printer(port_info.friendly_name);
+ }
+ CFRelease(cf_property);
+ }
+ if (port_info.friendly_name.empty())
+ port_info.friendly_name = port_info.port;
+
+ // Attempt to read out the VID & PID
+ int vid, pid;
+ auto cf_vendor = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idVendor"),
+ kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
+ auto cf_product = IORegistryEntrySearchCFProperty(port, kIOServicePlane, CFSTR("idProduct"),
+ kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents);
+ if (cf_vendor && cf_product) {
+ if (CFNumberGetValue((CFNumberRef)cf_vendor, kCFNumberIntType, &vid) &&
+ CFNumberGetValue((CFNumberRef)cf_product, kCFNumberIntType, &pid)) {
+ port_info.id_vendor = vid;
+ port_info.id_product = pid;
+ }
+ }
+ if (cf_vendor) { CFRelease(cf_vendor); }
+ if (cf_product) { CFRelease(cf_product); }
+
+ output.emplace_back(std::move(port_info));
+ }
+ }
+ IOObjectRelease(port);
+ }
+ }
+ }
+#else
+ // UNIX / Linux
+ std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" };
+ for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) {
+ std::string name = dir_entry.path().filename().string();
+ for (const char *prefix : prefixes) {
+ if (boost::starts_with(name, prefix)) {
+ const auto path = dir_entry.path().string();
+ SerialPortInfo spi;
+ spi.port = path;
+#ifdef __linux__
+ auto friendly_name = sysfs_tty_prop(name, "product");
+ if (friendly_name) {
+ spi.is_printer = looks_like_printer(*friendly_name);
+ spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str();
+ } else {
+ spi.friendly_name = path;
+ }
+ auto vid = sysfs_tty_prop_hex(name, "idVendor");
+ auto pid = sysfs_tty_prop_hex(name, "idProduct");
+ if (vid && pid) {
+ spi.id_vendor = *vid;
+ spi.id_product = *pid;
+ }
+#else
+ spi.friendly_name = path;
+#endif
+ output.emplace_back(std::move(spi));
+ break;
+ }
+ }
+ }
+#endif
+
+ output.erase(std::remove_if(output.begin(), output.end(),
+ [](const SerialPortInfo &info) {
+ return boost::starts_with(info.port, "Bluetooth") || boost::starts_with(info.port, "FireFly");
+ }),
+ output.end());
+ return output;
+}
+
+std::vector<std::string> scan_serial_ports()
+{
+ std::vector<SerialPortInfo> ports = scan_serial_ports_extended();
+ std::vector<std::string> output;
+ output.reserve(ports.size());
+ for (const SerialPortInfo &spi : ports)
+ output.emplace_back(std::move(spi.port));
+ return output;
+}
+
+
+
+// Class Serial
+
+namespace asio = boost::asio;
+using boost::system::error_code;
+
+Serial::Serial(asio::io_service& io_service) :
+ asio::serial_port(io_service)
+{}
+
+Serial::Serial(asio::io_service& io_service, const std::string &name, unsigned baud_rate) :
+ asio::serial_port(io_service, name)
+{
+ set_baud_rate(baud_rate);
+}
+
+Serial::~Serial() {}
+
+void Serial::set_baud_rate(unsigned baud_rate)
+{
+ try {
+ // This does not support speeds > 115200
+ set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
+ } catch (boost::system::system_error &) {
+ auto handle = native_handle();
+
+ auto handle_errno = [](int retval) {
+ if (retval != 0) {
+ throw std::runtime_error(
+ (boost::format("Could not set baud rate: %1%") % strerror(errno)).str()
+ );
+ }
+ };
+
+#if __APPLE__
+ termios ios;
+ handle_errno(::tcgetattr(handle, &ios));
+ handle_errno(::cfsetspeed(&ios, baud_rate));
+ speed_t newSpeed = baud_rate;
+ handle_errno(::ioctl(handle, IOSSIOSPEED, &newSpeed));
+ handle_errno(::tcsetattr(handle, TCSANOW, &ios));
+#elif __linux
+
+ /* The following definitions are kindly borrowed from:
+ /usr/include/asm-generic/termbits.h
+ Unfortunately we cannot just include that one because
+ it would redefine the "struct termios" already defined
+ the <termios.h> already included by Boost.ASIO. */
+#define K_NCCS 19
+ struct termios2 {
+ tcflag_t c_iflag;
+ tcflag_t c_oflag;
+ tcflag_t c_cflag;
+ tcflag_t c_lflag;
+ cc_t c_line;
+ cc_t c_cc[K_NCCS];
+ speed_t c_ispeed;
+ speed_t c_ospeed;
+ };
+#define BOTHER CBAUDEX
+
+ termios2 ios;
+ handle_errno(::ioctl(handle, TCGETS2, &ios));
+ ios.c_ispeed = ios.c_ospeed = baud_rate;
+ ios.c_cflag &= ~CBAUD;
+ ios.c_cflag |= BOTHER | CLOCAL | CREAD;
+ ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
+ ios.c_cc[VTIME] = 1;
+ handle_errno(::ioctl(handle, TCSETS2, &ios));
+
+#elif __OpenBSD__
+ struct termios ios;
+ handle_errno(::tcgetattr(handle, &ios));
+ handle_errno(::cfsetspeed(&ios, baud_rate));
+ handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios));
+#else
+ throw std::runtime_error("Custom baud rates are not currently supported on this OS");
+#endif
+ }
+}
+
+void Serial::set_DTR(bool on)
+{
+ auto handle = native_handle();
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) {
+ throw std::runtime_error("Could not set serial port DTR");
+ }
+#else
+ int status;
+ if (::ioctl(handle, TIOCMGET, &status) == 0) {
+ on ? status |= TIOCM_DTR : status &= ~TIOCM_DTR;
+ if (::ioctl(handle, TIOCMSET, &status) == 0) {
+ return;
+ }
+ }
+
+ throw std::runtime_error(
+ (boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str()
+ );
+#endif
+}
+
+void Serial::reset_line_num()
+{
+ // See https://github.com/MarlinFirmware/Marlin/wiki/M110
+ write_string("M110 N0\n");
+ m_line_num = 0;
+}
+
+bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec)
+{
+ auto &io_service = get_io_service();
+ asio::deadline_timer timer(io_service);
+ char c = 0;
+ bool fail = false;
+
+ while (true) {
+ io_service.reset();
+
+ asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) {
+ if (ec || size == 0) {
+ fail = true;
+ ec = read_ec; // FIXME: only if operation not aborted
+ }
+ timer.cancel(); // FIXME: ditto
+ });
+
+ if (timeout > 0) {
+ timer.expires_from_now(boost::posix_time::milliseconds(timeout));
+ timer.async_wait([&](const error_code &ec) {
+ // Ignore timer aborts
+ if (!ec) {
+ fail = true;
+ this->cancel();
+ }
+ });
+ }
+
+ io_service.run();
+
+ if (fail) {
+ return false;
+ } else if (c != '\n') {
+ line += c;
+ } else {
+ return true;
+ }
+ }
+}
+
+void Serial::printer_setup()
+{
+ printer_reset();
+ write_string("\r\r\r\r\r\r\r\r\r\r"); // Gets rid of line noise, if any
+}
+
+size_t Serial::write_string(const std::string &str)
+{
+ // TODO: might be wise to timeout here as well
+ return asio::write(*this, asio::buffer(str));
+}
+
+bool Serial::printer_ready_wait(unsigned retries, unsigned timeout)
+{
+ std::string line;
+ error_code ec;
+
+ for (; retries > 0; retries--) {
+ reset_line_num();
+
+ while (read_line(timeout, line, ec)) {
+ if (line == "ok") {
+ return true;
+ }
+ line.clear();
+ }
+
+ line.clear();
+ }
+
+ return false;
+}
+
+size_t Serial::printer_write_line(const std::string &line, unsigned line_num)
+{
+ const auto formatted_line = Utils::Serial::printer_format_line(line, line_num);
+ return write_string(formatted_line);
+}
+
+size_t Serial::printer_write_line(const std::string &line)
+{
+ m_line_num++;
+ return printer_write_line(line, m_line_num);
+}
+
+void Serial::printer_reset()
+{
+ this->set_DTR(false);
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ this->set_DTR(true);
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ this->set_DTR(false);
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+}
+
+std::string Serial::printer_format_line(const std::string &line, unsigned line_num)
+{
+ const auto line_num_str = std::to_string(line_num);
+
+ unsigned checksum = 'N';
+ for (auto c : line_num_str) { checksum ^= c; }
+ checksum ^= ' ';
+ for (auto c : line) { checksum ^= c; }
+
+ return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str();
+}
+
+
+} // namespace Utils
+} // namespace Slic3r
diff --git a/src/slic3r/Utils/Serial.hpp b/src/slic3r/Utils/Serial.hpp
new file mode 100644
index 000000000..e4a28de09
--- /dev/null
+++ b/src/slic3r/Utils/Serial.hpp
@@ -0,0 +1,82 @@
+#ifndef slic3r_GUI_Utils_Serial_hpp_
+#define slic3r_GUI_Utils_Serial_hpp_
+
+#include <vector>
+#include <string>
+#include <boost/system/error_code.hpp>
+#include <boost/asio.hpp>
+
+
+namespace Slic3r {
+namespace Utils {
+
+struct SerialPortInfo {
+ std::string port;
+ unsigned id_vendor = -1;
+ unsigned id_product = -1;
+ std::string friendly_name;
+ bool is_printer = false;
+
+ bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
+};
+
+inline bool operator==(const SerialPortInfo &sp1, const SerialPortInfo &sp2)
+{
+ return
+ sp1.port == sp2.port &&
+ sp1.id_vendor == sp2.id_vendor &&
+ sp1.id_product == sp2.id_product &&
+ sp1.is_printer == sp2.is_printer;
+}
+
+extern std::vector<std::string> scan_serial_ports();
+extern std::vector<SerialPortInfo> scan_serial_ports_extended();
+
+
+class Serial : public boost::asio::serial_port
+{
+public:
+ Serial(boost::asio::io_service &io_service);
+ Serial(boost::asio::io_service &io_service, const std::string &name, unsigned baud_rate);
+ Serial(const Serial &) = delete;
+ Serial &operator=(const Serial &) = delete;
+ ~Serial();
+
+ void set_baud_rate(unsigned baud_rate);
+ void set_DTR(bool on);
+
+ // Resets the line number both internally as well as with the firmware using M110
+ void reset_line_num();
+
+ // Reads a line or times out, the timeout is in milliseconds
+ bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec);
+
+ // Perform an initial setup for communicating with a printer
+ void printer_setup();
+
+ // Write data from a string
+ size_t write_string(const std::string &str);
+
+ // Attempts to reset the line numer and waits until the printer says "ok"
+ bool printer_ready_wait(unsigned retries, unsigned timeout);
+
+ // Write Marlin-formatted line, with a line number and a checksum
+ size_t printer_write_line(const std::string &line, unsigned line_num);
+
+ // Same as above, but with internally-managed line number
+ size_t printer_write_line(const std::string &line);
+
+ // Toggles DTR to reset the printer
+ void printer_reset();
+
+ // Formats a line Marlin-style, ie. with a sequential number and a checksum
+ static std::string printer_format_line(const std::string &line, unsigned line_num);
+private:
+ unsigned m_line_num = 0;
+};
+
+
+} // Utils
+} // Slic3r
+
+#endif /* slic3r_GUI_Utils_Serial_hpp_ */
diff --git a/src/slic3r/Utils/Time.cpp b/src/slic3r/Utils/Time.cpp
new file mode 100644
index 000000000..f38c4b407
--- /dev/null
+++ b/src/slic3r/Utils/Time.cpp
@@ -0,0 +1,80 @@
+#include "Time.hpp"
+
+#ifdef WIN32
+ #define WIN32_LEAN_AND_MEAN
+ #include <windows.h>
+ #undef WIN32_LEAN_AND_MEAN
+#endif /* WIN32 */
+
+namespace Slic3r {
+namespace Utils {
+
+time_t parse_time_ISO8601Z(const std::string &sdate)
+{
+ int y, M, d, h, m, s;
+ if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6)
+ return (time_t)-1;
+ struct tm tms;
+ tms.tm_year = y - 1900; // Year since 1900
+ tms.tm_mon = M - 1; // 0-11
+ tms.tm_mday = d; // 1-31
+ tms.tm_hour = h; // 0-23
+ tms.tm_min = m; // 0-59
+ tms.tm_sec = s; // 0-61 (0-60 in C++11)
+ return mktime(&tms);
+}
+
+std::string format_time_ISO8601Z(time_t time)
+{
+ struct tm tms;
+#ifdef WIN32
+ gmtime_s(&tms, &time);
+#else
+ gmtime_r(&time, &tms);
+#endif
+ char buf[128];
+ sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ",
+ tms.tm_year + 1900,
+ tms.tm_mon + 1,
+ tms.tm_mday,
+ tms.tm_hour,
+ tms.tm_min,
+ tms.tm_sec);
+ return buf;
+}
+
+std::string format_local_date_time(time_t time)
+{
+ struct tm tms;
+#ifdef WIN32
+ localtime_s(&tms, &time);
+#else
+ localtime_r(&time, &tms);
+#endif
+ char buf[80];
+ strftime(buf, 80, "%x %X", &tms);
+ return buf;
+}
+
+time_t get_current_time_utc()
+{
+#ifdef WIN32
+ SYSTEMTIME st;
+ ::GetSystemTime(&st);
+ std::tm tm;
+ tm.tm_sec = st.wSecond;
+ tm.tm_min = st.wMinute;
+ tm.tm_hour = st.wHour;
+ tm.tm_mday = st.wDay;
+ tm.tm_mon = st.wMonth - 1;
+ tm.tm_year = st.wYear - 1900;
+ tm.tm_isdst = -1;
+ return mktime(&tm);
+#else
+ const time_t current_local = time(nullptr);
+ return mktime(gmtime(&current_local));
+#endif
+}
+
+}; // namespace Utils
+}; // namespace Slic3r
diff --git a/src/slic3r/Utils/Time.hpp b/src/slic3r/Utils/Time.hpp
new file mode 100644
index 000000000..7b670bd3e
--- /dev/null
+++ b/src/slic3r/Utils/Time.hpp
@@ -0,0 +1,25 @@
+#ifndef slic3r_Utils_Time_hpp_
+#define slic3r_Utils_Time_hpp_
+
+#include <string>
+#include <time.h>
+
+namespace Slic3r {
+namespace Utils {
+
+// Utilities to convert an UTC time_t to/from an ISO8601 time format,
+// useful for putting timestamps into file and directory names.
+// Returns (time_t)-1 on error.
+extern time_t parse_time_ISO8601Z(const std::string &s);
+extern std::string format_time_ISO8601Z(time_t time);
+
+// Format the date and time from an UTC time according to the active locales and a local time zone.
+extern std::string format_local_date_time(time_t time);
+
+// There is no gmtime() on windows.
+extern time_t get_current_time_utc();
+
+}; // namespace Utils
+}; // namespace Slic3r
+
+#endif /* slic3r_Utils_Time_hpp_ */