protected override void Write(MamlRenderer renderer, MathBlock obj) { string formulaText = string.Empty; // obj.Content.Text.Substring(obj.Content.Start, obj.Content.Length); for (int i = 0; i < obj.Lines.Count; ++i) { var l = obj.Lines.Lines[i]; formulaText += l.Slice.Text.Substring(l.Slice.Start, l.Slice.Length); } if (string.IsNullOrEmpty(formulaText)) { return; } var formulaService = Current.GetRequiredService <ILaTeXFormulaImageStreamProvider>(); var(stream, placement, offset, width, height) = formulaService.Parse(formulaText, renderer.BodyTextFontFamily, renderer.BodyTextFontSize, 192, renderer.IsIntendedForHelp1File); if (null == stream) { return; } stream.Seek(0, SeekOrigin.Begin); var streamHash = MemoryStreamImageProxy.ComputeStreamHash(stream); stream.Seek(0, SeekOrigin.Begin); try { renderer.StorePngImageFile(stream, streamHash); stream.Close(); } finally { stream.Dispose(); } // now render to Maml file string localUrl = "../media/" + streamHash + ".png"; var attributes = new Dictionary <string, string> { { "src", localUrl }, { "width", System.Xml.XmlConvert.ToString(width) }, { "height", System.Xml.XmlConvert.ToString(height) } }; renderer.Push(MamlElements.markup); renderer.Push(MamlElements.a, new[] { new KeyValuePair <string, string>("href", renderer.ImageTopicFileGuid + ".htm#" + streamHash) }); renderer.Push(MamlElements.img, attributes); renderer.PopTo(MamlElements.markup); }
/// <summary> /// Exports the specified <see cref="TextDocument"/> to an external markdown file. /// </summary> /// <param name="document">The document to export.</param> /// <param name="fileName">Full name of the markdown file to export to.</param> public void Export(TextDocument document, string fileName) { var path = Path.GetDirectoryName(fileName); var imagePath = GetImagePath(path); var sourceDoc = new System.Text.StringBuilder(document.SourceText); var list = new List <(string Url, int urlSpanStart, int urlSpanEnd)>(document.ReferencedImageUrls); list.Sort((x, y) => Comparer <int> .Default.Compare(y.urlSpanEnd, x.urlSpanEnd)); // Note the inverse order of x and y to sort urlSpanEnd descending var imageStreamProvider = new ImageStreamProvider(); // Export images foreach (var(Url, urlSpanStart, urlSpanEnd) in list) { using (var stream = new System.IO.MemoryStream()) { var streamResult = imageStreamProvider.GetImageStream(stream, Url, 300, Altaxo.Main.ProjectFolder.GetFolderPart(document.Name), document.Images); if (streamResult.IsValid) { var hashName = MemoryStreamImageProxy.ComputeStreamHash(stream); var imageFileName = hashName + streamResult.Extension; if (!Directory.Exists(imagePath)) { Directory.CreateDirectory(imagePath); } // Copy stream to FileSystem using (var fileStream = new System.IO.FileStream(Path.Combine(imagePath, imageFileName), FileMode.Create, FileAccess.Write, FileShare.Read)) { stream.Seek(0, SeekOrigin.Begin); stream.CopyTo(fileStream); } // now change the url in the markdown text var newUrl = ImageDirectoryName + "/" + imageFileName; sourceDoc.Remove(urlSpanStart, 1 + urlSpanEnd - urlSpanStart); sourceDoc.Insert(urlSpanStart, newUrl); } } } // now save the markdown document using (var markdownStream = new System.IO.StreamWriter(fileName, false, Encoding.UTF8, 4096)) { markdownStream.Write(sourceDoc.ToString()); } }
ExportImages(TextDocument document, string basePathName) { var imagePath = GetImagePath(basePathName); var list = new List <(string Url, int urlSpanStart, int urlSpanEnd)>(document.ReferencedImageUrls); list.Sort((x, y) => Comparer <int> .Default.Compare(y.urlSpanEnd, x.urlSpanEnd)); // Note the inverse order of x and y to sort urlSpanEnd descending var imageStreamProvider = new ImageStreamProvider(); var oldToNewImageUrl = new Dictionary <string, string>(); var listOfReferencedImageFileNames = new HashSet <string>(); // Export images foreach (var(Url, urlSpanStart, urlSpanEnd) in list) { using (var stream = new System.IO.MemoryStream()) { var streamResult = imageStreamProvider.GetImageStream(stream, Url, 300, Altaxo.Main.ProjectFolder.GetFolderPart(document.Name), document.Images); if (streamResult.IsValid) { var hashName = MemoryStreamImageProxy.ComputeStreamHash(stream); var imageFileName = hashName + streamResult.Extension; if (!Directory.Exists(imagePath)) { Directory.CreateDirectory(imagePath); } // Copy stream to FileSystem var fullImageFileName = Path.Combine(imagePath, imageFileName); using (var fileStream = new System.IO.FileStream(fullImageFileName, FileMode.Create, FileAccess.Write, FileShare.Read)) { stream.Seek(0, SeekOrigin.Begin); stream.CopyTo(fileStream); } // now change the url in the markdown text var newUrl = ImageFolderName + "/" + imageFileName; oldToNewImageUrl[Url] = newUrl; listOfReferencedImageFileNames.Add(fullImageFileName); } } } return(oldToNewImageUrl, listOfReferencedImageFileNames); }
public string InsertImageInDocumentAndGetUrl(System.IO.Stream memoryStream, string fileExtension) { var imageProxy = MemoryStreamImageProxy.FromStream(memoryStream, fileExtension); return(TextDocument.AddImage(imageProxy)); }
public string InsertImageInDocumentAndGetUrl(string fileName) { var imageProxy = MemoryStreamImageProxy.FromFile(fileName); return(TextDocument.AddImage(imageProxy)); }
/// <summary> /// Expands the document to include child documents directly. This process is recursively, i.e. if the child documents contain child-child documents, /// they are expanded, too. /// </summary> /// <param name="textDocument">The original text document. This document is not changed during the expansion.</param> /// <param name="convertGraphsToImages">If true, links to Altaxo graphs will be converted to images. If false, the links to the graphs were kept, but the path to the graphs is changed appropriately.</param> /// <param name="newPath">Folder path of the final document that is the target of the expansion process.</param> /// <param name="recursionLevel">The recursion level. Start with 0 here.</param> /// <param name="errors">A list that collects error messages.</param> /// <returns>A new <see cref="TextDocument"/>. This text document contains the expanded markdown text. In addition, all Altaxo graphs are converted to local images.</returns> /// <remarks>Since finding Altaxo graphs embedded in the markdown is depended on the context (location of the TextDocument and location of the graph), /// and we somewhat loose this context during the expansion, we convert the graphs to local images before we insert the document into the master document.</remarks> private static TextDocument ExpandDocumentToNewDocument(TextDocument textDocument, bool convertGraphsToImages, string newPath, int recursionLevel = 0, List <MarkdownError> errors = null) { var resultDocument = new TextDocument(); resultDocument.AddImagesFrom(textDocument); resultDocument.Name = textDocument.Name; // first parse the document with markdig var pipeline = new MarkdownPipelineBuilder(); pipeline = MarkdownUtilities.UseSupportedExtensions(pipeline); var builtPipeline = pipeline.Build(); var markdownDocument = Markdig.Markdown.Parse(textDocument.SourceText, builtPipeline); var markdownToProcess = new List <MarkdownObject>(); foreach (var mdo in MarkdownUtilities.EnumerateAllMarkdownObjectsRecursively(markdownDocument)) { if (mdo is LinkInline link) { if (link.Url.ToLowerInvariant().StartsWith(ImagePretext.GraphRelativePathPretext)) { markdownToProcess.Add(mdo); } } else if (mdo is CodeBlock blk) { var attr = (Markdig.Renderers.Html.HtmlAttributes)mdo.GetData(typeof(Markdig.Renderers.Html.HtmlAttributes)); if (attr != null && attr.Properties != null && attr.Properties.Count >= 2 && attr.Properties[0].Key == "Altaxo" && attr.Properties[1].Key == "child") { var childDoc = attr.Properties[1].Value; if (null != childDoc) { markdownToProcess.Add(mdo); } } } } // now we process the list backwards and change the source var documentAsStringBuilder = new StringBuilder(textDocument.SourceText); var imageStreamProvider = new ImageStreamProvider(); markdownToProcess.Reverse(); // we start from the end of the document, in order not to change the positions of unprocessed markdown foreach (var mdo in markdownToProcess) { if (mdo is LinkInline link) { if (convertGraphsToImages) // convert links to graphs to images { using (var stream = new System.IO.MemoryStream()) { var streamResult = imageStreamProvider.GetImageStream(stream, link.Url, 300, Altaxo.Main.ProjectFolder.GetFolderPart(textDocument.Name), textDocument.Images); if (null == streamResult.ErrorMessage) { stream.Seek(0, System.IO.SeekOrigin.Begin); var proxy = MemoryStreamImageProxy.FromStream(stream, streamResult.Extension); resultDocument.AddImage(proxy); documentAsStringBuilder.Remove(link.UrlSpan.Value.Start, link.UrlSpan.Value.Length); documentAsStringBuilder.Insert(link.UrlSpan.Value.Start, "local:" + proxy.ContentHash); } } } else // keep link to graphs, but change their path { var newUrl = ConvertGraphUrl(link.Url, textDocument.Name, newPath); if (newUrl != link.Url) { documentAsStringBuilder.Remove(link.UrlSpan.Value.Start, link.UrlSpan.Value.Length); documentAsStringBuilder.Insert(link.UrlSpan.Value.Start, newUrl); } } } else if (mdo is CodeBlock blk) { var attr = (Markdig.Renderers.Html.HtmlAttributes)mdo.GetData(typeof(Markdig.Renderers.Html.HtmlAttributes)); var childDocName = attr.Properties[1].Value; // first, we assume a relative name var fullName = Altaxo.Main.ProjectFolder.GetFolderPart(textDocument.Name) + childDocName; var success = Current.Project.TextDocumentCollection.TryGetValue(fullName, out var childTextDocument); if (!success) // relative name failed, we try it with the unmodified (absolute) name { success = Current.Project.TextDocumentCollection.TryGetValue(childDocName, out childTextDocument); } if (success) { var expandedChild = ExpandDocumentToNewDocument(childTextDocument, convertGraphsToImages, newPath, recursionLevel + 1, errors); // exchange the source text documentAsStringBuilder.Remove(mdo.Span.Start, mdo.Span.Length); documentAsStringBuilder.Insert(mdo.Span.Start, expandedChild.SourceText); // insert images resultDocument.AddImagesFrom(expandedChild); } else if (null != errors) // report an error { var error = new MarkdownError() { AltaxoDocumentName = textDocument.Name, LineNumber = blk.Line, ColumnNumber = blk.Column, ErrorMessage = string.Format("Could not expand child document \"{0}\" because this name could not be resolved!", childDocName) }; errors.Add(error); } } } resultDocument.SourceText = documentAsStringBuilder.ToString(); if (0 == recursionLevel) // if we are about to return the master document, we must restore the list of referenced image Urls { markdownDocument = Markdig.Markdown.Parse(resultDocument.SourceText, builtPipeline); resultDocument.ReferencedImageUrls = MarkdownUtilities.GetReferencedImageUrls(markdownDocument); } return(resultDocument); }
protected override void Write(MamlRenderer renderer, MathInline obj) { var formulaText = obj.Content.Text.Substring(obj.Content.Start, obj.Content.Length); if (string.IsNullOrEmpty(formulaText)) { return; } var formulaService = Current.GetRequiredService <ILaTeXFormulaImageStreamProvider>(); var(stream, placement, offset, width, height) = formulaService.Parse(formulaText, renderer.BodyTextFontFamily, renderer.BodyTextFontSize, 192, renderer.IsIntendedForHelp1File); if (null == stream) { return; } stream.Seek(0, SeekOrigin.Begin); var streamHash = MemoryStreamImageProxy.ComputeStreamHash(stream); stream.Seek(0, SeekOrigin.Begin); try { renderer.StorePngImageFile(stream, streamHash); stream.Close(); } finally { stream.Dispose(); } // now render to Maml file string localUrl = "../media/" + streamHash + ".png"; var attributes = new Dictionary <string, string> { { "src", localUrl }, { "width", System.Xml.XmlConvert.ToString(width) }, { "height", System.Xml.XmlConvert.ToString(height) } }; if (renderer.IsIntendedForHelp1File) { attributes.Add("align", placement); } else { attributes.Add("style", string.Format(System.Globalization.CultureInfo.InvariantCulture, "vertical-align:{0}px;", offset)); } renderer.Push(MamlElements.markup); renderer.Push(MamlElements.a, new[] { new KeyValuePair <string, string>("href", renderer.ImageTopicFileGuid + ".htm#" + streamHash) }); renderer.Push(MamlElements.img, attributes); renderer.PopTo(MamlElements.markup); }
public string Import(string fileName) { var errors = new System.Text.StringBuilder(); string markdownDirectory = System.IO.Path.GetDirectoryName(fileName); string markdownText = null; using (var stream = new StreamReader(fileName, Encoding.UTF8, true)) { markdownText = stream.ReadToEnd(); } // Parse the markdown var pipeline = new MarkdownPipelineBuilder(); pipeline = UseSupportedExtensions(pipeline); var markdownDocument = Markdig.Markdown.Parse(markdownText, pipeline.Build()); var textDocument = new TextDocument(); var images = new List <(Markdig.Syntax.Inlines.LinkInline link, MemoryStreamImageProxy proxy)>(); foreach (var mdo in EnumerateAllMarkdownObjectsRecursively(markdownDocument)) { if (mdo is Markdig.Syntax.Inlines.LinkInline link && link.IsImage) { var url = link.Url; if (string.IsNullOrEmpty(url) || !Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) { continue; } var uri = new Uri(url, UriKind.RelativeOrAbsolute); string imgFileName = null; if (uri.IsAbsoluteUri) { imgFileName = uri.AbsolutePath; } else { imgFileName = System.IO.Path.Combine(markdownDirectory, uri.OriginalString); } try { var imgProxy = MemoryStreamImageProxy.FromFile(imgFileName); textDocument.AddImage(imgProxy); images.Add((link, imgProxy)); } catch (Exception ex) { errors.AppendFormat("File {0} could not be imported, error: {1}\r\n", imgFileName, ex.Message); } } } // now we have to replace all urls var text = new System.Text.StringBuilder(markdownText); for (int i = images.Count - 1; i >= 0; --i) { var(link, proxy) = images[i]; text.Remove(link.UrlSpan.Value.Start, link.UrlSpan.Value.End + 1 - link.UrlSpan.Value.Start); text.Insert(link.UrlSpan.Value.Start, ImagePretext.LocalImagePretext + proxy.ContentHash); } textDocument.SourceText = text.ToString(); Current.Project.TextDocumentCollection.Add(textDocument); Current.ProjectService.ShowDocumentView(textDocument); return(errors.Length == 0 ? null : errors.ToString()); }