#include "Designers.h" #define LAYOUTFILE #include struct ExportMD : WithExportMDLayout { bool exporting; String qtf; String name; String md; Vector img; void Export(const RichPara& p); void Export(const RichTable& table, const RichStyles& styles); void Export(const RichText& txt); static bool IsPreformatted(const RichPara& p); void Do(const char *qtf, const char *name); ExportMD(); }; ExportMD::ExportMD() { CtrlLayoutExit(*this, "Export as GitHub Markdown"); sel.SetImage(Upp::CtrlImg::Dir()); sel << [=] { dir <<= Nvl(SelectDirectory(), ~~dir); }; list.NoWantFocus(); doexport << [=] { exporting = true; Export(ParseQTF(qtf)); String d = ~dir; if(!DirectoryExists(d)) { if(!PromptYesNo("Create directory [* \1 " + d)) return; if(!RealizeDirectory(d)) Exclamation("Cannot create [* \1 " + d); } Progress pi("Exporting", 2 * img.GetCount()); SaveFile(d + "/" + name + ".md", md); for(int i = 0; i < img.GetCount(); i++) { for(int half = 0; half < 2; half++) { if(pi.StepCanceled()) return; RichObjectPaintInfo pi; pi.ink = SBlack(); Image m = img[i].ToImage(img[i].GetPixelSize(), pi); PNGEncoder().SaveFile(d + "/" + AsString(i) + (half ? "_half.png" : ".png"), half ? Downscale2x(m) : m); } } exporting = false; Export(ParseQTF(qtf)); }; } void ExportMD::Export(const RichPara& p) { if(p.format.ruler) md << "---"; if(p.format.bullet) md << "- "; for(int i = 0; i < p.part.GetCount(); i++) { const RichPara::Part& part = p.part[i]; if(part.object) { int n = img.GetCount(); if(exporting) md << "![IMAGE " << n << "](" << n << ".png)"; else md << "IMAGE:" << n; img << part.object; } else { const wchar *s = part.text; while(*s == ' ') { md << ' '; s++; } String endtag; if(part.format.sscript == 1) { md << ""; endtag = "" + endtag; } if(part.format.sscript == 2) { md << ""; endtag = "" + endtag; } if(part.format.IsUnderline()) { md << ""; endtag = "" + endtag; } if(part.format.IsBold()) { if(!md.TrimEnd("**")) md << "**"; endtag = "**" + endtag; } if(part.format.IsItalic()) { md << "_"; endtag = "_" + endtag; } if(part.format.IsStrikeout()) { md << "~~"; endtag = "~~" + endtag; } while(*s) { auto NeedsEscape = [](int c) { return strchr("|[]{}^*_<>~-`'\"", c); }; if(*s == '\\' && NeedsEscape(s[1])) md << "\\\\" << (char)*++s; else if(NeedsEscape(*s)) md << "\\" << (char)*s; else md << ToUtf8(*s); s++; } if(endtag.GetCount()) { String t; while(md.TrimEnd(" ")) t << ' '; md << endtag << t; } } } } void ExportMD::Export(const RichTable& table, const RichStyles& styles) { for(int i = 0; i < table.GetRows(); i++) { for(int j = 0; j < table.GetColumns(); j++) { md << "|"; const RichTxt& txt = table.Get(i, j); for(int i = 0; i < txt.GetPartCount(); i++) if(txt.IsPara(i)) Export(txt.Get(i, styles)); } md << "|\n"; if(i == 0) { for(int j = 0; j < table.GetColumns(); j++) md << "|-"; md << "|\n"; } } md << "\n\n"; } bool ExportMD::IsPreformatted(const RichPara& p) { bool b = false; for(int i = 0; i < p.part.GetCount(); i++) { const RichPara::Part& part = p.part[i]; if(part.object || !(part.format.GetFaceInfo() & Font::FIXEDPITCH)) return false; b = b || part.text.GetCount(); } return b; } void ExportMD::Export(const RichText& txt) { img.Clear(); md.Clear(); int i = 0; while(i < txt.GetPartCount()) if(txt.IsPara(i)) { const RichPara& p = txt.Get(i++); if(IsPreformatted(p)) { md << "```\n"; md << ToUtf8(p.GetText()) << '\n'; while(i < txt.GetPartCount() && txt.IsPara(i)) { const RichPara& p = txt.Get(i); if(!IsPreformatted(p)) break; i++; md << ToUtf8(p.GetText()) << '\n'; } md << "```\n"; } else { Export(p); md << "\n\n"; } } else if(txt.IsTable(i)) Export(txt.GetTable(i++), txt.GetStyles()); else i++; } void ExportMD::Do(const char *qtf_, const char *name_) { name = name_; dir <<= GetHomeDirectory() + "/" + name; qtf = qtf_; Export(ParseQTF(qtf)); WriteClipboardText(md); if(img.GetCount()) { list.SetLineCy(DPI(28)); list.AddColumn("Image").Ctrls([&](int ii, One& ctrl) { int half = ii & 1; ii /= 2; Button& b = ctrl.Create