-
Notifications
You must be signed in to change notification settings - Fork 0
/
XamlRender.cs
142 lines (118 loc) · 4.37 KB
/
XamlRender.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using System.IO;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace XamlToBitmap.Render
{
public interface IContentRender
{
/// <summary>
/// Render XAML template with binded data context
/// </summary>
/// <param name="stream">Stream with content of XAML template</param>
/// <param name="dataContext">Data object to bind to XAML template</param>
/// <param name="dpiX">Horizontal resolution by X</param>
/// <param name="dpiY">Vertical resolution by Y</param>
/// <returns>Binary stream with PNG image content</returns>
public Task<MemoryStream> RenderAsync(
Stream stream,
object dataContext,
double dpiX, double dpiY );
}
public class XamlRender : IContentRender
{
public static FrameworkElement LoadFrameworkElement( Stream stream )
=> XamlReader.Load( stream ) as FrameworkElement
?? throw new Exception( "Can not load XAML! Root have to be instance of the FrameworkElement class." );
public static MemoryStream RenderContent(
FrameworkElement content,
double dpiX, double dpiY )
{
var widthPx =
content.ActualWidth
.ToPixelsExact( dpiX );
var heightPx =
content.ActualHeight
.ToPixelsExact( dpiY );
// Negative margins in XAML might lead to issues
// when a content of printing lays out of template bounds
var targetBitmap =
new RenderTargetBitmap(
widthPx, heightPx,
dpiX, dpiY,
PixelFormats.Pbgra32 );
targetBitmap.Render( content );
var encoder = new PngBitmapEncoder();
encoder.Frames.Add( BitmapFrame.Create( targetBitmap ) );
MemoryStream mem = new();
encoder.Save( mem );
return mem;
}
public static MemoryStream Render(
Stream stream, object dataContext,
double dpiX, double dpiY )
{
var root =
LoadFrameworkElement( stream )
.AssignDataContext( dataContext )
.ResizeLayout();
var mem = RenderContent( root, dpiX, dpiY );
root.DataContext = null;
root.RaiseEvent( new RoutedEventArgs( FrameworkElement.UnloadedEvent ) );
root.Dispatcher.InvokeShutdown();
return mem;
}
public async Task<MemoryStream> RenderAsync(
Stream stream, object dataContext,
double dpiX, double dpiY )
{
MemoryStream mem =
await StaTask.Run(
() => Render( stream, dataContext, dpiX, dpiY ) );
return mem
?? throw new Exception( "Can not render source." );
}
public const float WindowsScreen_DefaultDpi = 96f;
public const float ThermalPrinter_CommonDpi = 203f;
}
public static class StaTask
{
public static Task<T> Run<T>( Func<T> func )
{
var tcs = new TaskCompletionSource<T>();
Thread thread = new( () => {
try {
tcs.SetResult( func() );
}
catch ( Exception e ) {
tcs.SetException( e );
}
} );
thread.SetApartmentState( ApartmentState.STA );
thread.Start();
return tcs.Task;
}
}
file static class Extensions
{
public static int ToPixelsExact( this double actualSize, double dpi )
=> (int)( actualSize * ( dpi / XamlRender.WindowsScreen_DefaultDpi ) );
public static FrameworkElement AssignDataContext( this FrameworkElement e, object dataContext )
{
e.DataContext = dataContext;
return e;
}
public static FrameworkElement ResizeLayout( this FrameworkElement content )
{
content.Measure(
new Size(
double.PositiveInfinity
, double.PositiveInfinity ) );
content.Arrange(
new Rect( content.DesiredSize ) );
content.UpdateLayout();
return content;
}
}
}