private static void onHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var owner = (RichTextBlock)d; var status = owner.GetStatus(); if (status == null) { status = new RichTextBlockStatus(); owner.SetStatus(status); owner.Loaded += Owner_Loaded; owner.Unloaded += Owner_Unloaded; status.RequestedThemePropertyToken = owner.RegisterPropertyChangedCallback(FrameworkElement.RequestedThemeProperty, onRequestedThemeChanged); status.ForegroundToken = owner.RegisterPropertyChangedCallback(RichTextBlock.ForegroundProperty, onForegroundChanged); owner.SizeChanged += onSizeChanged; } var newValue = e.NewValue as string; if (string.IsNullOrWhiteSpace(newValue)) return; var html = (HtmlDocument)HtmlParser.Parse(newValue, true); owner.Blocks.Clear(); status.BaseUri = owner.GetBaseUri(); status.Foreground = owner.Foreground; status.ActualWidth = owner.ActualWidth; status.RequestedTheme = owner.RequestedTheme; status.Images.Clear(); status.Hyperlinks.Clear(); status.Lines.Clear(); status.HyperlinkButtons.Clear(); var paragraph = new Paragraph(); CreateChildren(paragraph.Inlines, html.Body, status); owner.Blocks.Add(paragraph); }
private static void SetStatus(this FrameworkElement @this, RichTextBlockStatus value) => @this.SetValue(StatusPorperty, value);
private static void CreateElement(InlineCollection parent, Node node, RichTextBlockStatus status) { switch (node) { case Element element: switch (element.TagName.ToLower()) { case "a": { // We want to change BaseUri easily, so we use `GetAttribute("href")` instead of `HtmlAnchorElement.Href` var href = element.GetAttribute("href"); if (!status.TryCreateUri(href, out var uri)) break; parent.Add(new Run() { Text = " " }); var hyperlink = new Hyperlink() { NavigateUri = uri, Foreground = status.Foreground }; foreach (var child in element.ChildNodes) { switch (child) { case HtmlImageElement childElement: if (CreateImage(element, status) is Image image) { if (hyperlink.Inlines.Count != 0) { hyperlink.SetUri(href); status.Hyperlinks.Add(hyperlink); parent.Add(hyperlink); } var button = new HyperlinkButton() { NavigateUri = uri, Content = image, RequestedTheme = status.RequestedTheme }; button.SetUri(href); status.HyperlinkButtons.Add(button); parent.Add(new InlineUIContainer() { Child = button }); hyperlink = new Hyperlink() { NavigateUri = uri, Foreground = status.Foreground }; } break; default: CreateElement(hyperlink.Inlines, child, status); break; } break; } if (hyperlink.Inlines.Count != 0) { hyperlink.SetUri(href); status.Hyperlinks.Add(hyperlink); parent.Add(hyperlink); } parent.Add(new Run() { Text = " " }); } break; case "img": { if (CreateImage(element, status) is Image image) parent.Add(new InlineUIContainer() { Child = image }); } break; case "strong": case "b": { var span = new Span() { FontWeight = FontWeights.Bold }; CreateChildren(span.Inlines, element, status); parent.Add(span); } break; case "div": case "font": case "p": case "span": { var span = new Span(); foreach (var s in ParseStyle(element.GetAttribute("style"))) switch (s.Key) { case "font-size": { var value = s.Value; double fontSize; if (value.EndsWith("px")) fontSize = double.Parse(value.Remove(value.Length - 2)); else if (value.EndsWith("%")) fontSize = 14 * double.Parse(value.Remove(value.Length - 1)) / 100; else fontSize = 14 * double.Parse(value); span.FontSize = fontSize; } break; case "font-weight": switch (s.Value) { case "bold": span.FontWeight = FontWeights.Bold; break; } break; } CreateChildren(span.Inlines, element, status); parent.Add(span); } break; case "br": if (element.NextSibling is Text nextText && nextText.Data.StartsWith("\n")) break; parent.Add(new LineBreak()); break; case "hr": parent.Add(new LineBreak()); var line = new Border() { BorderThickness = new Thickness(0, 1, 0, 0), BorderBrush = status.Foreground, Margin = new Thickness(8, 0, 8, 0), Height = 1, }; if (status.ActualWidth > 16) line.Width = status.ActualWidth - 16; status.Lines.Add(line); parent.Add(new InlineUIContainer() { Child = line }); parent.Add(new LineBreak()); break; case "iframe": // Ignore case "script": case "noscript": break; #if DEBUG default: Debug.WriteLine($"Ignore unknown tag {element.TagName}"); break; #endif } break; case Text text: parent.Add(new Run() { Text = text.Data }); break; } }
private static void CreateChildren(InlineCollection parent, Element node, RichTextBlockStatus status) { foreach (var child in node.ChildNodes) CreateElement(parent, child, status); }
private static Image CreateImage(Element image, RichTextBlockStatus status) { var src = image.GetAttribute("src"); if (!status.TryCreateUri(src, out var uri)) return null; if (!uri.Scheme.StartsWith("http")) return null; var result = new Image() { Source = UriToBitmapImageConverter.Instance.Convert(uri), MaxWidth = status.ActualWidth }; result.SetUri(src); result.ImageOpened += Result_ImageOpened; if (image.HasAttribute("width")) result.Width = double.Parse(image.GetAttribute("width")); else result.Width = 0; if (image.HasAttribute("height")) result.Height = double.Parse(image.GetAttribute("height")); else result.Height = 0; status.Images.Add(result); return result; }