diff options
Diffstat (limited to 'src/slic3r/Utils')
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, ®DataType, (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(¤t_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_ */ |