using AngleSharp; using AngleSharp.Css.Dom; using AngleSharp.Dom; using AngleSharp.Html.Parser; using CefSharpWrap; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Windows.Forms; namespace MarkdownRenderer { public partial class MarkdownRendererForm : Form { private readonly CefSharpWrapBrowser _rendererBrowser; private readonly HtmlParser _agHtmlParser; private string _html; private double _scrollPosition; private string _styleSheet; private string _bodyClassName; public MarkdownRendererForm() { InitializeComponent(); _rendererBrowser = CefSharpWrapFactory.CreateSealedInstance($"{CefSharpWrapFactory.SchemeName}://{CefSharpWrapFactory.CustomResourceDomainName}/{MarkdownRendererMain.CustomResourcePath}/"); _rendererBrowser.FillToControl(MainPanel); _rendererBrowser.RegisterJsObject("BridgeObject", new RenderJsObjectBridge(this)); _rendererBrowser.ExternalRequested += delegate (object sender, string fileName) { try { Process.Start(fileName); } catch (Exception e) { MessageBox.Show(e.ToString(), "External execute fail", MessageBoxButtons.OK, MessageBoxIcon.Error); } }; var agContext = BrowsingContext.New(Configuration.Default.WithDefaultLoader().WithCss()); _agHtmlParser = new HtmlParser(new HtmlParserOptions(), agContext); FillStyleSheetDropDownMenuItems(BuiltInStyleSheetsBulidinToolStripMenuItem, StyleSheetStore.BuiltInEntries); RefreshCustomStyleSheetMenuItems(); //TODO: Active last used style sheet SetStyleSheet(StyleSheetStore.BuiltInEntries[0]); } private void SetStyleSheet(StyleSheetEntry styleSheet) { _styleSheet = styleSheet.Css; _bodyClassName = styleSheet.BodyClass; _rendererBrowser.ExecuteJsAsync("syncStyle()"); } public void PutMarkdown(string mdText) { var action = new Action(() => { _html = MarkdownHtmlConverter.ToHtml(mdText); _rendererBrowser.ExecuteJsAsync("syncHtml()"); }); if (InvokeRequired) Invoke(action); else action(); } public void ScrollToPercent(double scrollPosition) { //TODO: Sync scroll position match to section. too hard to implement _scrollPosition = scrollPosition; _rendererBrowser.ExecuteJsAsync("syncScroll()"); } private void RefreshCustomStyleSheetMenuItems() { for (var i = CustomStyleSheetNoneToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--) { var item = CustomStyleSheetNoneToolStripMenuItem.DropDownItems[i]; if (item != CustomStyleSheetNoneToolStripMenuItem) CustomStyleSheetNoneToolStripMenuItem.DropDownItems.Remove(item); } var entries = StyleSheetStore.GetEntries(); if (false == (CustomStyleSheetNoneToolStripMenuItem.Visible = entries.Length == 0)) { FillStyleSheetDropDownMenuItems(BuiltInStyleSheetsBulidinToolStripMenuItem, entries); } } private static void FillStyleSheetDropDownMenuItems(ToolStripDropDownItem container, IEnumerable entries) { foreach (var entry in entries) { var item = new ToolStripMenuItem { Text = entry.Name, Tag = entry }; //TODO: restore check state container.DropDownItems.Add(item); } } private string GenerateHtmlForExport() { var htmlTemplate = Properties.Resources.TemplatePageForExport; var doc = _agHtmlParser.ParseDocument(htmlTemplate); doc.QuerySelector("style").InnerHtml = _styleSheet; var body = doc.QuerySelector("body"); body.InnerHtml = _html; body.ClassName = _bodyClassName; using (var writer = new StringWriter()) { doc.ToHtml(writer); return writer.ToString(); } } private void CopyToolStripButton_Click(object sender, EventArgs e) { try { var html = GenerateHtmlForExport(); Clipboard.SetText(html); } catch (Exception exception) { MessageBox.Show(exception.ToString(), "Copy HTML fail", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void CopyInlineStyleHtml_Click(object sender, EventArgs e) { try { var html = GenerateHtmlForExport(); var doc = _agHtmlParser.ParseDocument(html); //TODO: check why pre and quote missing background-image foreach (var element in doc.QuerySelectorAll("body,body *")) { var style = doc.DefaultView.GetComputedStyle(element); element.SetStyle(style.CssText.Replace('\"', '\'')); element.RemoveAttribute("class"); } foreach (var element in doc.QuerySelectorAll("style")) { element.Remove(); } var writer = new StringWriter(); doc.ToHtml(writer); var processedHtml = writer.ToString(); Clipboard.SetText(processedHtml); } catch (Exception exception) { MessageBox.Show(exception.ToString(), "Copy HTML fail", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void SaveToolStripButton_Click(object sender, EventArgs e) { var sfd = new SaveFileDialog { Title = "Save as HTML", Filter = "HTML files (*.html;*.htm)|*.htm;*.html" }; if (DialogResult.OK != sfd.ShowDialog()) return; try { var html = GenerateHtmlForExport(); File.WriteAllText(sfd.FileName, html); } catch (Exception exception) { MessageBox.Show(exception.ToString(), "Save as HTML fail", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void PrintToolStripButton_Click(object sender, EventArgs e) { new PrintToPdfForm(_rendererBrowser).ShowDialog(); } private void StyleSheetManageToolStripMenuItem_Click(object sender, EventArgs e) { new StyleSheetForm().ShowDialog(); //TODO: if current active entry was delete (not found in custom and bulit-in), choose an other one or reset to first built-in RefreshCustomStyleSheetMenuItems(); } private void HelpToolStripButton_Click(object sender, EventArgs e) { new AboutForm().ShowDialog(); } private void F12ToolStripButton_Click(object sender, EventArgs e) { _rendererBrowser.ShowF12(); } private void MarkdownRendererForm_FormClosed(object sender, FormClosedEventArgs e) { _rendererBrowser.Dispose(); } private void StyleSheetsToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) { foreach (ToolStripMenuItem item in CustomStyleSheetNoneToolStripMenuItem.DropDownItems) item.Checked = false; foreach (ToolStripMenuItem item in BuiltInStyleSheetsBulidinToolStripMenuItem.DropDownItems) item.Checked = false; var clickedItem = (ToolStripMenuItem)e.ClickedItem; clickedItem.Checked = true; SetStyleSheet((StyleSheetEntry)clickedItem.Tag); //TODO: Save currelt selected style sheet id to config store } private class RenderJsObjectBridge { private readonly MarkdownRendererForm _formInstance; public RenderJsObjectBridge(MarkdownRendererForm formInstance) => _formInstance = formInstance; // ReSharper disable UnusedMember.Local public string StyleSheet => _formInstance._styleSheet; public string BodyClassName => _formInstance._bodyClassName; public string Html => _formInstance._html; public double ScrollPosition => _formInstance._scrollPosition; // ReSharper restore UnusedMember.Local } } }