MarkdownViewerRenderer.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. using System;
  2. using System.IO;
  3. using Svg;
  4. using System.Drawing;
  5. using System.Drawing.Imaging;
  6. using System.Net;
  7. using System.Threading;
  8. using TheArtOfDev.HtmlRenderer.Core.Entities;
  9. using static com.insanitydesign.MarkdownViewerPlusPlus.MarkdownViewer;
  10. /// <summary>
  11. ///
  12. /// </summary>
  13. namespace com.insanitydesign.MarkdownViewerPlusPlus.Forms
  14. {
  15. /// <summary>
  16. ///
  17. /// </summary>
  18. public class MarkdownViewerRenderer : AbstractRenderer
  19. {
  20. /// <summary>
  21. ///
  22. /// </summary>
  23. public MarkdownViewerHtmlPanel markdownViewerHtmlPanel;
  24. /// <summary>
  25. ///
  26. /// </summary>
  27. /// <param name="markdownViewer"></param>
  28. public MarkdownViewerRenderer(MarkdownViewer markdownViewer) : base(markdownViewer)
  29. {
  30. }
  31. /// <summary>
  32. ///
  33. /// </summary>
  34. protected override void Init()
  35. {
  36. base.Init();
  37. //
  38. this.markdownViewerHtmlPanel = new MarkdownViewerHtmlPanel();
  39. //Add a custom image loader
  40. this.markdownViewerHtmlPanel.ImageLoad += OnImageLoad;
  41. //Add to view
  42. this.Controls.Add(this.markdownViewerHtmlPanel);
  43. this.Controls.SetChildIndex(this.markdownViewerHtmlPanel, 0);
  44. }
  45. /// <summary>
  46. ///
  47. /// </summary>
  48. /// <param name="text"></param>
  49. /// <param name="fileInfo"></param>
  50. public override void Render(string text, FileInformation fileInfo)
  51. {
  52. base.Render(text, fileInfo);
  53. this.markdownViewerHtmlPanel.Text = BuildHtml(ConvertedText, fileInfo.FileName);
  54. }
  55. /// <summary>
  56. /// Scroll the rendered panel vertically based on the given ration
  57. /// taken from Notepad++
  58. /// </summary>
  59. /// <param name="scrollRatio"></param>
  60. public override void ScrollByRatioVertically(double scrollRatio)
  61. {
  62. this.markdownViewerHtmlPanel.ScrollByRatioVertically(scrollRatio);
  63. }
  64. /// <summary>
  65. /// Custom renderer for SVG images in the markdown as not supported natively.
  66. /// @see https://htmlrenderer.codeplex.com/wikipage?title=Rendering%20SVG%20images
  67. /// </summary>
  68. /// <param name="sender"></param>
  69. /// <param name="imageLoadEvent"></param>
  70. protected void OnImageLoad(object sender, HtmlImageLoadEventArgs imageLoadEvent)
  71. {
  72. try
  73. {
  74. //Get some file information
  75. string src = imageLoadEvent.Src;
  76. Uri uri = new Uri(src);
  77. string extension = Path.GetExtension(src);
  78. //Check if local file or web resource
  79. switch (uri.Scheme.ToLowerInvariant())
  80. {
  81. case "file":
  82. //In case of a local file -> Try to load it directly
  83. imageLoadEvent.Handled = true; //Tell the event it was handled, so no error border is drawn
  84. ThreadPool.QueueUserWorkItem(state => LoadImageFromFile(src, imageLoadEvent));
  85. break;
  86. case "http":
  87. case "https":
  88. //For web resources check extension and parameter, to fetch from e.g. "badge" creating sources
  89. if ((extension != null && extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
  90. || uri.ToString().Contains("svg="))
  91. {
  92. //In case of a web resource file -> Load async
  93. using (WebClient webClient = new WebClient())
  94. {
  95. imageLoadEvent.Handled = true; //Tell the event it was handled, so no error border is drawn
  96. webClient.DownloadDataCompleted += (downloadSender, downloadEvent) => { OnDownloadDataCompleted(downloadEvent, imageLoadEvent); };
  97. webClient.DownloadDataAsync(uri);
  98. }
  99. }
  100. break;
  101. }
  102. }
  103. catch
  104. {
  105. }
  106. }
  107. /// <summary>
  108. ///
  109. /// </summary>
  110. /// <param name="src"></param>
  111. /// <param name="imageLoadEvent"></param>
  112. protected void LoadImageFromFile(string src, HtmlImageLoadEventArgs imageLoadEvent)
  113. {
  114. try
  115. {
  116. Uri uri = new Uri(src);
  117. //Try to load the file as Image from file
  118. //Remove the scheme first
  119. string srcWithoutScheme = src;
  120. int i = srcWithoutScheme.IndexOf(':');
  121. if (i > 0) srcWithoutScheme = srcWithoutScheme.Substring(i + 1).TrimStart('/');
  122. //If not absolute, add the current file path
  123. if (!Path.IsPathRooted(srcWithoutScheme))
  124. {
  125. uri = new Uri(@"file:///" + this.FileInfo.FileDirectory + "/" + srcWithoutScheme);
  126. }
  127. //For SVG images: Convert to Bitmap
  128. string extension = Path.GetExtension(src);
  129. if (extension != null && extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
  130. {
  131. ConvertSvgToBitmap(SvgDocument.Open<SvgDocument>(uri.LocalPath), imageLoadEvent);
  132. }
  133. else
  134. {
  135. //Load uri, 8, 1
  136. imageLoadEvent.Callback((Bitmap)Image.FromFile(uri.LocalPath, true));
  137. }
  138. }
  139. catch { } //Not able to handle, refer back to orginal process
  140. }
  141. /// <summary>
  142. ///
  143. /// </summary>
  144. /// <param name="svgDocument"></param>
  145. /// <param name="imageLoadEvent"></param>
  146. protected Bitmap ConvertSvgToBitmap(SvgDocument svgDocument, HtmlImageLoadEventArgs imageLoadEvent)
  147. {
  148. Bitmap svgImage = new Bitmap((int)svgDocument.Width, (int)svgDocument.Height, PixelFormat.Format32bppArgb);
  149. svgDocument.Draw(svgImage);
  150. imageLoadEvent.Callback(svgImage);
  151. imageLoadEvent.Handled = true;
  152. return svgImage;
  153. }
  154. /// <summary>
  155. ///
  156. /// </summary>
  157. /// <param name="downloadEvent"></param>
  158. /// <param name="imageLoadEvent"></param>
  159. protected void OnDownloadDataCompleted(DownloadDataCompletedEventArgs downloadEvent, HtmlImageLoadEventArgs imageLoadEvent)
  160. {
  161. using (MemoryStream stream = new MemoryStream(downloadEvent.Result))
  162. {
  163. ConvertSvgToBitmap(SvgDocument.Open<SvgDocument>(stream), imageLoadEvent);
  164. }
  165. }
  166. /// <summary>
  167. /// Release the custom loader
  168. /// </summary>
  169. protected override void Dispose(bool disposing)
  170. {
  171. if (this.markdownViewerHtmlPanel != null)
  172. {
  173. this.markdownViewerHtmlPanel.ImageLoad -= OnImageLoad;
  174. }
  175. base.Dispose(disposing);
  176. }
  177. }
  178. }