static Common() { BoldItalicTextInline.AddTripChars(_triggerList); BoldTextInline.AddTripChars(_triggerList); ItalicTextInline.AddTripChars(_triggerList); MarkdownLinkInline.AddTripChars(_triggerList); HyperlinkInline.AddTripChars(_triggerList); CommentInline.AddTripChars(_triggerList); StrikethroughTextInline.AddTripChars(_triggerList); SuperscriptTextInline.AddTripChars(_triggerList); SubscriptTextInline.AddTripChars(_triggerList); CodeInline.AddTripChars(_triggerList); ImageInline.AddTripChars(_triggerList); EmojiInline.AddTripChars(_triggerList); LinkAnchorInline.AddTripChars(_triggerList); // Create an array of characters to search against using IndexOfAny. _tripCharacters = _triggerList.Select(trigger => trigger.FirstChar).Distinct().ToArray(); }
/// <summary> /// Renders an image element. /// </summary> /// <param name="element"> The parsed inline element to render. </param> /// <param name="context"> Persistent state. </param> protected override async void RenderImage(ImageInline element, IRenderContext context) { var localContext = context as InlineRenderContext; if (localContext == null) { throw new RenderContextIncorrectException(); } var inlineCollection = localContext.InlineCollection; var placeholder = InternalRenderTextRun(new TextRunInline { Text = element.Text, Type = MarkdownInlineType.TextRun }, context); var resolvedImage = await ImageResolver.ResolveImageAsync(element.RenderUrl, element.Tooltip); // if image can not be resolved we have to return if (resolvedImage == null) { return; } var image = new Image { Source = resolvedImage, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Stretch = ImageStretch }; HyperlinkButton hyperlinkButton = new HyperlinkButton() { Content = image }; var viewbox = new Viewbox { Child = hyperlinkButton, StretchDirection = StretchDirection.DownOnly }; viewbox.PointerWheelChanged += Preventative_PointerWheelChanged; var scrollViewer = new ScrollViewer { Content = viewbox, VerticalScrollMode = ScrollMode.Disabled, VerticalScrollBarVisibility = ScrollBarVisibility.Disabled }; var imageContainer = new InlineUIContainer() { Child = scrollViewer }; bool ishyperlink = element.RenderUrl != element.Url; LinkRegister.RegisterNewHyperLink(image, element.Url, ishyperlink); if (ImageMaxHeight > 0) { viewbox.MaxHeight = ImageMaxHeight; } if (ImageMaxWidth > 0) { viewbox.MaxWidth = ImageMaxWidth; } if (element.ImageWidth > 0) { image.Width = element.ImageWidth; image.Stretch = Stretch.UniformToFill; } if (element.ImageHeight > 0) { if (element.ImageWidth == 0) { image.Width = element.ImageHeight; } image.Height = element.ImageHeight; image.Stretch = Stretch.UniformToFill; } if (element.ImageHeight > 0 && element.ImageWidth > 0) { image.Stretch = Stretch.Fill; } // If image size is given then scroll to view overflown part if (element.ImageHeight > 0 || element.ImageWidth > 0) { scrollViewer.HorizontalScrollMode = ScrollMode.Auto; scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; } // Else resize the image else { scrollViewer.HorizontalScrollMode = ScrollMode.Disabled; scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled; } ToolTipService.SetToolTip(image, element.Tooltip); // Try to add it to the current inlines // Could fail because some containers like Hyperlink cannot have inlined images try { var placeholderIndex = inlineCollection.IndexOf(placeholder); inlineCollection.Remove(placeholder); inlineCollection.Insert(placeholderIndex, imageContainer); } catch { // Ignore error } }
/// <summary> /// Attempts to parse an image e.g. "![Toolkit logo](https://raw.githubusercontent.com/windows-toolkit/WindowsCommunityToolkit/master/Microsoft.Toolkit.Uwp.SampleApp/Assets/ToolkitLogo.png)". /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location to start parsing. </param> /// <param name="end"> The location to stop parsing. </param> /// <returns> A parsed markdown image, or <c>null</c> if this is not a markdown image. </returns> internal static InlineParseResult Parse(string markdown, int start, int end) { // Expect a '!' character. if (start >= end || markdown[start] != '!') { return(null); } int pos = start + 1; // Then a '[' character if (pos >= end || markdown[pos] != '[') { return(null); } pos++; // Find the ']' character while (pos < end) { if (markdown[pos] == ']') { break; } pos++; } if (pos == end) { return(null); } // Extract the alt. string tooltip = markdown.Substring(start + 2, pos - (start + 2)); // Expect the '(' character. pos++; string reference = string.Empty; string url = string.Empty; int imageWidth = 0; int imageHeight = 0; if (pos < end && markdown[pos] == '[') { int refstart = pos; // Find the reference ']' character while (pos < end) { if (markdown[pos] == ']') { break; } pos++; } reference = markdown.Substring(refstart + 1, pos - refstart - 1); } else if (pos < end && markdown[pos] == '(') { while (pos < end && ParseHelpers.IsMarkdownWhiteSpace(markdown[pos])) { pos++; } // Extract the URL. int urlStart = pos; while (pos < end && markdown[pos] != ')') { pos++; } var imageDimensionsPos = markdown.IndexOf(" =", urlStart, pos - urlStart, StringComparison.Ordinal); url = imageDimensionsPos > 0 ? TextRunInline.ResolveEscapeSequences(markdown, urlStart + 1, imageDimensionsPos) : TextRunInline.ResolveEscapeSequences(markdown, urlStart + 1, pos); if (imageDimensionsPos > 0) { // trying to find 'x' which separates image width and height var dimensionsSepatorPos = markdown.IndexOf("x", imageDimensionsPos + 2, pos - imageDimensionsPos - 1, StringComparison.Ordinal); // didn't find separator, trying to parse value as imageWidth if (dimensionsSepatorPos == -1) { var imageWidthStr = markdown.Substring(imageDimensionsPos + 2, pos - imageDimensionsPos - 2); int.TryParse(imageWidthStr, out imageWidth); } else { var dimensions = markdown.Substring(imageDimensionsPos + 2, pos - imageDimensionsPos - 2).Split('x'); // got width and height if (dimensions.Length == 2) { int.TryParse(dimensions[0], out imageWidth); int.TryParse(dimensions[1], out imageHeight); } } } } if (pos == end) { return(null); } // We found something! var result = new ImageInline { Tooltip = tooltip, RenderUrl = url, ReferenceId = reference, Url = url, Text = markdown.Substring(start, pos + 1 - start), ImageWidth = imageWidth, ImageHeight = imageHeight }; return(new InlineParseResult(result, start, pos + 1)); }
/// <summary> /// Finds the next inline element by matching trip chars and verifying the match. /// </summary> /// <param name="markdown"> The markdown text to parse. </param> /// <param name="start"> The position to start parsing. </param> /// <param name="end"> The position to stop parsing. </param> /// <param name="ignoreLinks"> Indicates whether to parse links. </param> /// <returns>Returns the next element</returns> private static InlineParseResult FindNextInlineElement(string markdown, int start, int end, bool ignoreLinks) { // Search for the next inline sequence. for (int pos = start; pos < end; pos++) { // IndexOfAny should be the fastest way to skip characters we don't care about. pos = markdown.IndexOfAny(_tripCharacters, pos, end - pos); if (pos < 0) { break; } // Find the trigger(s) that matched. char currentChar = markdown[pos]; foreach (InlineTripCharHelper currentTripChar in _triggerList) { // Check if our current char matches the suffix char. if (currentChar == currentTripChar.FirstChar) { // Don't match if the previous character was a backslash. if (pos > start && markdown[pos - 1] == '\\') { continue; } // If we are here we have a possible match. Call into the inline class to verify. InlineParseResult parseResult = null; switch (currentTripChar.Method) { case InlineParseMethod.BoldItalic: parseResult = BoldItalicTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Comment: parseResult = CommentInline.Parse(markdown, pos, end); break; case InlineParseMethod.LinkReference: parseResult = LinkAnchorInline.Parse(markdown, pos, end); break; case InlineParseMethod.Bold: parseResult = BoldTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Italic: parseResult = ItalicTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.MarkdownLink: if (!ignoreLinks) { parseResult = MarkdownLinkInline.Parse(markdown, pos, end); } break; case InlineParseMethod.AngleBracketLink: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseAngleBracketLink(markdown, pos, end); } break; case InlineParseMethod.Url: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseUrl(markdown, pos, end); } break; case InlineParseMethod.RedditLink: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseRedditLink(markdown, pos, end); } break; case InlineParseMethod.PartialLink: if (!ignoreLinks) { parseResult = HyperlinkInline.ParsePartialLink(markdown, pos, end); } break; case InlineParseMethod.Email: if (!ignoreLinks) { parseResult = HyperlinkInline.ParseEmailAddress(markdown, start, pos, end); } break; case InlineParseMethod.Strikethrough: parseResult = StrikethroughTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Superscript: parseResult = SuperscriptTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Subscript: parseResult = SubscriptTextInline.Parse(markdown, pos, end); break; case InlineParseMethod.Code: parseResult = CodeInline.Parse(markdown, pos, end); break; case InlineParseMethod.Image: parseResult = ImageInline.Parse(markdown, pos, end); break; case InlineParseMethod.Emoji: parseResult = EmojiInline.Parse(markdown, pos, end); break; } if (parseResult != null) { return(parseResult); } } } } // If we didn't find any elements we have a normal text block. // Let us consume the entire range. return(new InlineParseResult(TextRunInline.Parse(markdown, start, end), start, end)); }