#define EXTERN_WX_GUI #include "transcode.h" IMPLEMENT_APP(TranscodeGUI) bool TranscodeGUI::OnInit() { #ifdef _WIN32 setlocale(LC_ALL, ".UTF8"); SetConsoleOutputCP(CP_UTF8); #else std::locale::global(std::locale("")); #endif TranscodeFrame *transcodeFrame = new TranscodeFrame; transcodeFrame->Show(true); SetTopWindow(transcodeFrame); return true; } TranscodeFrame::TranscodeFrame(): wxFrame(nullptr, wxID_ANY, wxT("SlavConvert: Transcoding Tool"), wxDefaultPosition, wxSize(640,480), wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER | wxMAXIMIZE_BOX)) { wxString config_dir = wxStandardPaths::Get().GetUserConfigDir(); config_filepath = config_dir + path_separator #ifdef _WIN32 + "SlavConvert.ini"; #else + ".slavconvertrc"; #endif read_config_file(config_filepath, config_options); config_params.do_greek = to_bool(config_options["greek"]); config_params.do_font_select = to_bool(config_options["fontsel"]); main_panel = new wxPanel(this, wxID_ANY); main_sizer = new wxBoxSizer(wxVERTICAL); infile_sizer = new wxBoxSizer(wxHORIZONTAL); infile_label = new wxStaticText(main_panel, wxID_ANY, wxT("File to Convert"), wxDefaultPosition, wxDefaultSize); infile_field = new wxTextCtrl(main_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCB_READONLY); infile_button = new wxButton(main_panel, wxID_ANY, wxT("Select")); main_sizer->Add(infile_label, 0, wxALL); infile_sizer->Add(infile_field, 2, wxEXPAND | wxALL); infile_sizer->Add(infile_button, 0, wxALL | wxALIGN_CENTER_VERTICAL); main_sizer->Add(infile_sizer, 0, wxEXPAND | wxALL, 2); wxString infile_from_cfg = config_options["in"]; if (wxFileExists(infile_from_cfg)) { infile_path = infile_from_cfg; infile_field->ChangeValue(infile_path); } tsvfile_sizer = new wxBoxSizer(wxHORIZONTAL); tsvfile_label = new wxStaticText(main_panel, wxID_ANY, wxT("Character Table (TSV)"), wxDefaultPosition, wxDefaultSize); tsvfile_field = new wxTextCtrl(main_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCB_READONLY); tsvfile_button = new wxButton(main_panel, wxID_ANY, wxT("Select")); main_sizer->Add(tsvfile_label, 0, wxALL); tsvfile_sizer->Add(tsvfile_field, 2, wxEXPAND | wxALL); tsvfile_sizer->Add(tsvfile_button, 0, wxALL | wxALIGN_CENTER_VERTICAL); main_sizer->Add(tsvfile_sizer, 0, wxEXPAND | wxALL, 2); wxString tsvfile_from_cfg = config_options["tsv"]; if (wxFileExists(tsvfile_from_cfg)) { tsvfile_path = tsvfile_from_cfg; tsvfile_field->ChangeValue(tsvfile_path); } fontsel_sizer = new wxBoxSizer(wxHORIZONTAL); fontsel_label = new wxStaticText(main_panel, wxID_ANY, wxT("Target Unicode Font"), wxDefaultPosition, wxDefaultSize); fontsel_field = new wxTextCtrl(main_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCB_READONLY); fontsel_name = config_options["font"]; if (fontsel_name.length() > 0) fontsel_field->ChangeValue(fontsel_name); fontsel_button = new wxButton(main_panel, wxID_ANY, wxT("Select")); main_sizer->Add(fontsel_label, 0, wxALL); fontsel_sizer->Add(fontsel_field, 2, wxEXPAND | wxALL); fontsel_sizer->Add(fontsel_button, 0, wxALL | wxALIGN_CENTER_VERTICAL); main_sizer->Add(fontsel_sizer, 0, wxEXPAND | wxALL, 2); infile_button->Bind(wxEVT_BUTTON, &TranscodeFrame::SelectDoc, this); tsvfile_button->Bind(wxEVT_BUTTON, &TranscodeFrame::SelectTSV, this); fontsel_button->Bind(wxEVT_BUTTON, &TranscodeFrame::DoFontDialog, this); check_greek = new wxCheckBox(main_panel, wxID_ANY, wxT("Convert Greek combining characters to precomposed")); check_greek->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &TranscodeFrame::ChkGrk, this); check_greek->SetValue(config_params.do_greek); main_sizer->Add(check_greek, 0, wxEXPAND, 2); check_fontsel = new wxCheckBox(main_panel, wxID_ANY, wxT("Display list of fonts to select prior to conversion")); check_fontsel->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &TranscodeFrame::ChkFontSel, this); check_fontsel->SetValue(config_params.do_font_select); main_sizer->Add(check_fontsel, 0, wxEXPAND, 2); transcode_button = new wxButton(main_panel, wxID_OK, wxT("Run")); transcode_button->Bind(wxEVT_BUTTON, &TranscodeFrame::Transcode, this); clear_button = new wxButton(main_panel, wxID_CLEAR, wxT("Clear")); clear_button->Bind(wxEVT_BUTTON, &TranscodeFrame::Reset, this); quit_button = new wxButton(main_panel, wxID_EXIT, wxT("Exit")); quit_button->Bind(wxEVT_BUTTON, &TranscodeFrame::Quit, this); button_sizer = new wxBoxSizer(wxHORIZONTAL); button_sizer->Add(transcode_button, 2, wxEXPAND | wxALL); button_sizer->Add(quit_button, 2, wxEXPAND | wxALL); button_sizer->Add(clear_button, 2, wxEXPAND | wxALL); main_sizer->Add(button_sizer, 0, wxEXPAND, 2); status_messages = new wxTextCtrl(main_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE); main_sizer->Add(status_messages, 2, wxEXPAND | wxALL, 5); main_panel->SetSizer(main_sizer); } void TranscodeFrame::Quit(wxCommandEvent& WXUNUSED(event)) { write_config_file(config_filepath, config_options); Close(true); } void TranscodeFrame::Reset(wxCommandEvent& WXUNUSED(event)) { status_messages->Clear(); infile_field->Clear(); config_options["in"] = ""; infile_path.Clear(); // wxString tsvfile_field->Clear(); tsvfile_path.Clear(); // wxString config_options["tsv"] = ""; fontsel_field->Clear(); fontsel_name.clear(); // wstring config_options["font"] = ""; write_config_file(config_filepath, config_options); } void TranscodeFrame::SelectDoc(wxCommandEvent& WXUNUSED(event)) { wxFileDialog docx_dialog(main_panel, wxT("Select Document to Convert"), wxEmptyString, wxEmptyString, "Word OpenXML Document (*.docx)|*.docx", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (docx_dialog.ShowModal() == wxID_OK) { infile_path = docx_dialog.GetPath(); infile_field->ChangeValue(infile_path); config_options["in"] = infile_path; write_config_file(config_filepath, config_options); } else if ( !(tsvfile_path.length() > 0 && !wxFileExists(tsvfile_path)) ) { wxMessageBox(wxT("Please select a valid file!"), wxT("Error"), wxOK | wxICON_ERROR); } } void TranscodeFrame::SelectTSV(wxCommandEvent& WXUNUSED(event)) { wxFileDialog tsv_dialog(this, wxT("Select Character Table File"), wxEmptyString, wxEmptyString, "Tab-Separated Data (*.tsv)|*.tsv", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (tsv_dialog.ShowModal() == wxID_OK) { tsvfile_path = tsv_dialog.GetPath(); tsvfile_field->ChangeValue(tsvfile_path); config_options["tsv"] = tsvfile_path; write_config_file(config_filepath, config_options); } else if ( !(tsvfile_path.length() > 0 && wxFileExists(tsvfile_path)) ) { wxMessageBox(wxT("Please select a valid file!"), wxT("Error"), wxOK | wxICON_ERROR); } } void TranscodeFrame::LoadCharmap(wxString& charfile, const wchar_t sep, map>& char_map, vector& utf_chars) { wxTextFile cfile; wxString lstr; vector fonts; wstring col; if (!cfile.Open(charfile)) { wxMessageBox(wxT("Could not open character table!"), wxT("File Error"), wxOK | wxICON_ERROR); return; } // initialize the map with font names from headers lstr = cfile.GetFirstLine(); wstringstream fline_ss(lstr.ToStdWstring()); // skip the first column, it's always UTF-8 getline(fline_ss, col, sep); while(getline(fline_ss, col, sep)) { char_map.insert({col, {}}); fonts.push_back(col); } // Now process the remaining lines while (!cfile.Eof()) { size_t cn = 0; lstr = cfile.GetNextLine(); wstringstream line_ss(lstr.ToStdWstring()); // the first column is always UTF-8 getline(line_ss, col, sep); utf_chars.push_back(col); while(getline(line_ss, col, sep)) { if (cn < fonts.size()) char_map[fonts[cn]].push_back(col); cn++; } } } void TranscodeFrame::DoFontDialog(wxCommandEvent& WXUNUSED(event)) { wxFontDialog font_dialog(this); if (font_dialog.ShowModal() == wxID_OK) { auto font_chosen = font_dialog.GetFontData().GetChosenFont(); fontsel_name = font_chosen.GetFaceName(); fontsel_field->ChangeValue(fontsel_name); } config_options["font"] = fontsel_name; write_config_file(config_filepath, config_options); } void TranscodeFrame::ChkGrk(wxCommandEvent& WXUNUSED(event)) { if (check_greek->GetValue()) { config_params.do_greek = true; config_options["greek"] = "1"; } else { config_params.do_greek = false; config_options["greek"] = "0"; } write_config_file(config_filepath, config_options); } void TranscodeFrame::ChkFontSel(wxCommandEvent& WXUNUSED(event)) { if (check_fontsel->GetValue()) { config_params.do_font_select = true; config_options["fontsel"] = "1"; } else { config_params.do_font_select = false; config_options["fontsel"] = "0"; } write_config_file(config_filepath, config_options); } void TranscodeFrame::ZipCreate(const wxString& files_dir, const wxString& zip_file) { wxFileOutputStream fos(zip_file); wxZipOutputStream zos(fos); wxDir dir(files_dir); wxArrayString files; dir.Open(files_dir); // we need hidden files (dotfiles), but can ignore empty dirs dir.GetAllFiles(files_dir, &files, wxEmptyString, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN); chdir(files_dir); for (wxString& file: files) { file.Replace(files_dir + path_separator, ""); wxFileInputStream fis(file); zos.PutNextEntry(file); zos.Write(fis); } } // based on example at https://wiki.wxwidgets.org/WxZipInputStream void TranscodeFrame::ZipExtract(const wxString& zip_file, const wxString& dir_target) { wxFileInputStream fis(zip_file); wxZipInputStream zis(fis); unique_ptr zptr; while (zptr.reset(zis.GetNextEntry()), zptr) // != nullptr { wxString zip_file = dir_target + wxFileName::GetPathSeparator() + zptr->GetName(); wxFileName fn; if (zptr->IsDir()) fn.AssignDir(zip_file); else fn.Assign(zip_file); if (!wxDirExists(fn.GetPath())) { wxFileName::Mkdir(fn.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL); } if (zptr->IsDir()) continue; wxFileOutputStream fos(zip_file); zis.Read(fos); } } void TranscodeFrame::Transcode(wxCommandEvent& WXUNUSED(event)) { vector doc_fonts, doc_fonts_sel, doc_encs, utf_chars; map> char_map; wxString cwd_0 = wxGetCwd(); // redirect stdout and stderr (wchar) to text field cerr.rdbuf(cout.rdbuf()); wxStreamToTextRedirector redirect(status_messages); if ( !(tsvfile_path.length() > 0 && infile_path.length() > 0 && fontsel_name.length() > 0) ) { wxMessageBox(wxT("Please select input file, character table, and font!"), wxT("Error"), wxOK | wxICON_ERROR); return; } wxFileName fn(infile_path); wxString outfile_dir = fn.GetPath(); wxString outfile_basename = fn.GetName(); wxString outfile_name = outfile_basename + "_UTF.docx"; wxString outfile_path; wxFileDialog outfile_dialog(this, wxT("Save Converted File"), outfile_dir, outfile_name, "Word OpenXML Document (*.docx)|*.docx", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (outfile_dialog.ShowModal() == wxID_CANCEL) return; else outfile_path = outfile_dialog.GetPath(); cout << "Processing file " << infile_path << endl; TranscodeFrame::LoadCharmap(tsvfile_path, L'\t', char_map, utf_chars); wxString tempdir = wxFileName::GetTempDir(); wxString temp_path = tempdir + path_separator + random_anum_string(6); wxMkdir(temp_path, wxS_DIR_DEFAULT); TranscodeFrame::ZipExtract(infile_path, temp_path); wxSetWorkingDirectory(temp_path); wstring default_paragraph_font; get_default_pfont(default_paragraph_font, styles_xmlfile); if (default_paragraph_font.length() > 0) doc_fonts.push_back(default_paragraph_font); for (const auto& convfile: convfiles) detect_fonts(convfile, doc_fonts, doc_encs); // We only want to get the unique fonts uniq(doc_fonts); uniq(doc_encs); cout << "Unique fonts discovered in document: " << endl; for (const auto& doc_font: doc_fonts) cout << " - " << doc_font << endl; wxArrayString flist_choices; for (const auto& f_wstr: doc_fonts) { if (char_map.count(f_wstr) != 0) flist_choices.Add(wxString(f_wstr.c_str())); } if (config_params.do_font_select) { wxMultiChoiceDialog flist_dialog(this, wxT("Select fonts to convert:"), wxT("Font Selection"), flist_choices, wxOK); flist_dialog.ShowModal(); wxArrayInt flist_sel = flist_dialog.GetSelections(); for (int index: flist_sel) doc_fonts_sel.push_back(flist_choices[index].ToStdWstring()); } else doc_fonts_sel = doc_fonts; for (const auto& convfile: convfiles) { if (!wxFileExists(convfile)) continue; cout << "Processing XML source file: " << convfile << endl; process_xml(convfile, fontsel_name.ToStdWstring(), doc_fonts_sel, doc_encs, char_map, utf_chars, default_paragraph_font, config_params.do_greek); } update_default_pfont(styles_xmlfile, fontsel_name.ToStdWstring(), doc_fonts); cout << "Conversion completed!" << endl; TranscodeFrame::ZipCreate(temp_path, outfile_path); wxFileName::Rmdir(temp_path, wxPATH_RMDIR_RECURSIVE); wxSetWorkingDirectory(cwd_0); write_config_file(config_filepath, config_options); }