Skip to content
This repository was archived by the owner on Mar 14, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Linq;
using System.Windows;
using Markdig.Renderers.Wpf;
using TableColumnAlign = Markdig.Extensions.Tables.TableColumnAlign;
using Table = Markdig.Extensions.Tables.Table;
using TableCell = Markdig.Extensions.Tables.TableCell;
using TableRow = Markdig.Extensions.Tables.TableRow;
using WpfDocs = System.Windows.Documents;


namespace Markdig.Wpf.SampleAppCustomized.Customized
{
public class ColumnScalingTableRenderer : WpfObjectRenderer<Table>
{
protected override void Write(Renderers.WpfRenderer renderer, Table table)
{
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
if (table == null) throw new ArgumentNullException(nameof(table));

var wpfTable = new WpfDocs.Table();

wpfTable.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.TableStyleKey);

#region Customization

var columnValueList = new System.Collections.Generic.List<string>?[table.ColumnDefinitions.Count];

#endregion

foreach (var tableColumnDefinition in table.ColumnDefinitions)
{
wpfTable.Columns.Add(new WpfDocs.TableColumn
{
Width = (tableColumnDefinition?.Width ?? 0) != 0 ?
new GridLength(tableColumnDefinition!.Width, GridUnitType.Star) :
GridLength.Auto,
});
}

var wpfRowGroup = new WpfDocs.TableRowGroup();

renderer.Push(wpfTable);
renderer.Push(wpfRowGroup);

foreach (var rowObj in table)
{
var row = (TableRow)rowObj;
var wpfRow = new WpfDocs.TableRow();

renderer.Push(wpfRow);

if (row.IsHeader)
{
wpfRow.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.TableHeaderStyleKey);
}

for (var i = 0; i < row.Count; i++)
{
#region Customization

if (i < columnValueList.Length - 1 && columnValueList[i] is null)
columnValueList[i] = new(row.Count);

#endregion

var cellObj = row[i];
var cell = (TableCell)cellObj;
var wpfCell = new WpfDocs.TableCell
{
ColumnSpan = cell.ColumnSpan,
RowSpan = cell.RowSpan,
};

wpfCell.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.TableCellStyleKey);

renderer.Push(wpfCell);
renderer.Write(cell);
renderer.Pop();

var txt = new WpfDocs.TextRange(wpfCell.ContentStart, wpfCell.ContentEnd).Text;
columnValueList[i]?.Add(txt);

if (table.ColumnDefinitions.Count > 0)
{
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i
: cell.ColumnIndex;
columnIndex = columnIndex >= table.ColumnDefinitions.Count ? table.ColumnDefinitions.Count - 1 : columnIndex;
var alignment = table.ColumnDefinitions[columnIndex].Alignment;


#region Customization

if (row.IsHeader)
alignment = TableColumnAlign.Center;

#endregion

if (alignment.HasValue)
{
switch (alignment)
{
case TableColumnAlign.Center:
wpfCell.TextAlignment = TextAlignment.Center;
break;
case TableColumnAlign.Right:
wpfCell.TextAlignment = TextAlignment.Right;
break;
case TableColumnAlign.Left:
wpfCell.TextAlignment = TextAlignment.Left;
break;
}
}
}
}

renderer.Pop();
}

#region Customization

var weights = columnValueList.Select(x => x?.Max(y => y.Length) ?? 0.0).ToArray();
var sum = weights.Sum();
for (int i = 0; i < weights.Length; i++)
{
var col = wpfTable.Columns[i];
var weight = weights[i];
col.Width = weight == 0
? new GridLength(0, GridUnitType.Pixel)
: new GridLength(weight / sum * 100.0, GridUnitType.Star);
}

#endregion

renderer.Pop();
renderer.Pop();
}


}
}
25 changes: 21 additions & 4 deletions src/Markdig.Wpf.SampleAppCustomized/Customized/MarkdownViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Markdig.Wpf.SampleAppCustomized.Customized
{
/// <summary>
/// Usually the image paths in the Markdown must be relative to the application or absolute.
/// This classes make it possible to change the root path.
/// This class utilizes a custom renderer and facilitates changing the root path.
/// </summary>
public class MarkdownViewer : Markdig.Wpf.MarkdownViewer
{
Expand All @@ -27,13 +27,30 @@ public string UCRootPath

protected override void RefreshDocument()
{
// In some cases the path is not updated fast enough, so we force it
Document = Markdown is null
? null
: Markdig.Wpf.Markdown.ToFlowDocument(Markdown, Pipeline ?? DefaultPipeline, _renderer);
}


private WpfRenderer? _renderer = null;
public void SetCustomRenderer(bool? state)
{
// For obvious contrast, we only provide a custom renderer in 1 of the 3 presentation states
if (state != true)
{
_renderer = null;
return;
}

// In some cases the UCRootPath is not updated fast enough, so we force it
this.GetBindingExpression(UCRootPathProperty)?.UpdateTarget();

var path = UCRootPath;
if (!string.IsNullOrEmpty(path) && !path.EndsWith("/", StringComparison.Ordinal))
path = path.Remove(path.LastIndexOf('/') + 1);
Document = Markdown != null ? Markdig.Wpf.Markdown.ToFlowDocument(Markdown, Pipeline ?? DefaultPipeline, new Customized.WpfRenderer(path)) : null;

_renderer = new WpfRenderer(new LinkInlineRenderer(path), new ColumnScalingTableRenderer());
}
}
}
21 changes: 15 additions & 6 deletions src/Markdig.Wpf.SampleAppCustomized/Customized/WpfRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
namespace Markdig.Wpf.SampleAppCustomized.Customized
{
/// <summary>
/// Create a custom Renderer instead of using the one in the Markdig.Wpf package
/// </summary>
public class WpfRenderer : Markdig.Renderers.WpfRenderer
{
private string _linkpath;
private readonly Renderers.IMarkdownObjectRenderer[] _injections;

/// <summary>
/// Initializes the WPF renderer
/// </summary>
/// <param name="linkpath">image path for the custom LinkInlineRenderer</param>
public WpfRenderer(string linkpath) : base()
/// <param name="injectRenderers">custom renderer injection list</param>
public WpfRenderer(params Renderers.IMarkdownObjectRenderer[] injectRenderers) : base()
{
_linkpath = linkpath;
_injections = injectRenderers;
}

/// <summary>
/// Load first the custom renderer's
/// Load our custom renderer's before the base versions. By doing this, our renderers can
/// handle/continue on that specific Markdig type before ever reaching the default renderer
/// </summary>
protected override void LoadRenderers()
{
ObjectRenderers.Add(new LinkInlineRenderer(_linkpath));
if (_injections.Length > 0)
{
foreach (var renderer in _injections)
ObjectRenderers.Add(renderer);
}

base.LoadRenderers();
}
}
Expand Down
100 changes: 96 additions & 4 deletions src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,107 @@
xmlns:local="clr-namespace:Markdig.Wpf.SampleAppCustomized.Customized"
xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow">
<Window.Resources>
<!-- Define some new styles based on the predefined style keys -->
<Style TargetType="Paragraph" x:Key="MyH1" BasedOn="{StaticResource {x:Static markdig:Styles.Heading1StyleKey}}">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontWeight" Value="ExtraBold"/>
<Setter Property="FontSize" Value="30"/>
</Style>
<Style TargetType="Paragraph" x:Key="MyH2" BasedOn="{StaticResource {x:Static markdig:Styles.Heading2StyleKey}}">
<Setter Property="Foreground" Value="LimeGreen"/>
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="26"/>
</Style>
<Style TargetType="Paragraph" x:Key="MyH3" BasedOn="{StaticResource {x:Static markdig:Styles.Heading3StyleKey}}">
<Setter Property="Foreground" Value="Blue"/>
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
<Style TargetType="TableRow" x:Key="MyTableHeader" BasedOn="{StaticResource {x:Static markdig:Styles.TableHeaderStyleKey}}">
<Setter Property="Background" Value="Gray"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
</Style>
<Style TargetType="TableCell" x:Key="MyTableCell" BasedOn="{StaticResource {x:Static markdig:Styles.TableCellStyleKey}}">
<Setter Property="Padding" Value="8,4"/>
</Style>

</Window.Resources>

<FrameworkElement.CommandBindings>
<CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" />
<CommandBinding Command="{x:Static markdig:Commands.Image}" Executed="ClickOnImage" />
</FrameworkElement.CommandBindings>

<DockPanel>
<Button x:Name="ToggleExtensionsButton" DockPanel.Dock="Top" HorizontalAlignment="Center"
Content="Toggle supported extensions" Click="ToggleExtensionsButton_OnClick" />

<local:MarkdownViewer x:Name="Viewer"/>
<ToggleButton x:Name="ToggleExtensionsButton"
DockPanel.Dock="Top" HorizontalAlignment="Center" Padding="5,1"
IsThreeState="True" IsChecked="{Binding PresentationState}">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Current State: all Pipeline extensions, custom Renderers &amp; Styles"/>
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter Property="Content" Value="Current State: no customization, no Pipeline extensions or Styles"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content" Value="Current State: no customization, just all supported Pipeline extensions"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>

<local:MarkdownViewer x:Name="Viewer">
<local:MarkdownViewer.Style>
<!-- Brought over the control style/template & hooked up custom style injection to toggle button -->
<Style TargetType="markdig:MarkdownViewer">
<Setter Property="Template">
<Setter.Value>
<!-- Default MarkdownViewer template -->
<ControlTemplate TargetType="markdig:MarkdownViewer">
<FlowDocumentScrollViewer Document="{TemplateBinding Document}"
ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=ToggleExtensionsButton}" Value="True">
<Setter Property="Template">
<Setter.Value>
<!-- Custom MarkdownViewer template that injects style key overrides -->
<ControlTemplate TargetType="markdig:MarkdownViewer">
<ControlTemplate.Resources>
<!-- Override some of the predefined style keys using our custom styles -->
<Style TargetType="Paragraph" x:Key="{x:Static markdig:Styles.Heading1StyleKey}"
BasedOn="{StaticResource MyH1}" />
<Style TargetType="Paragraph" x:Key="{x:Static markdig:Styles.Heading2StyleKey}"
BasedOn="{StaticResource MyH2}"/>
<Style TargetType="Paragraph" x:Key="{x:Static markdig:Styles.Heading3StyleKey}"
BasedOn="{StaticResource MyH3}" />
<Style TargetType="TableRow" x:Key="{x:Static markdig:Styles.TableHeaderStyleKey}"
BasedOn="{StaticResource MyTableHeader}" />
<Style TargetType="TableCell" x:Key="{x:Static markdig:Styles.TableCellStyleKey}"
BasedOn="{StaticResource MyTableCell}" />
</ControlTemplate.Resources>
<FlowDocumentScrollViewer Document="{TemplateBinding Document}"
ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</local:MarkdownViewer.Style>
</local:MarkdownViewer>
</DockPanel>
</Window>
30 changes: 25 additions & 5 deletions src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls.Primitives;

namespace Markdig.Wpf.SampleAppCustomized
{
Expand All @@ -9,7 +10,16 @@ namespace Markdig.Wpf.SampleAppCustomized
/// </summary>
public partial class MainWindow : Window
{
private bool useExtensions = true;
private bool? _presentationState = false;

/// <summary>
/// Represents the IsChecked 3-State ToggleButton that changes the presentation
/// </summary>
public bool? PresentationState
{
get => _presentationState;
set => ToggleExtensions(value);
}

public MainWindow()
{
Expand All @@ -34,12 +44,22 @@ private void ClickOnImage(object sender, System.Windows.Input.ExecutedRoutedEven
MessageBox.Show($"URL: {e.Parameter}");
}

private void ToggleExtensionsButton_OnClick(object sender, RoutedEventArgs e)
private void ToggleExtensions(bool? newValue)
{
useExtensions = !useExtensions;
Viewer.Pipeline = useExtensions ? new MarkdownPipelineBuilder().UseSupportedExtensions().Build() : new MarkdownPipelineBuilder().Build();
_presentationState = newValue;

// Setting the custom renderer first since the pipeline property will trigger refresh
Viewer.SetCustomRenderer(newValue);
Viewer.Pipeline = newValue switch
{
// Even if we include a custom renderer, we still have to enable the applicable
// Pipeline extensions that handle that Markdig type.
// In this case, UsePipeTables() enables the use of ColumnScalingTableRenderer
true => new MarkdownPipelineBuilder().UsePipeTables().Build(),
false => new MarkdownPipelineBuilder().UseSupportedExtensions().Build(),
_ => new MarkdownPipelineBuilder().Build()
};
}


}
}