├── README ├── WpfTextBenchmark.sln └── WpfTextBenchmark ├── App.xaml ├── App.xaml.cs ├── D2DWrapper.dll ├── Direct2DControl.cs ├── Direct2DTextMatrixView.cs ├── ExtTextOutMatrixView.cs ├── FormattedTextMatrixView.cs ├── GDIMatrixView.cs ├── GlyphMatrixView.cs ├── MatrixView.cs ├── Properties ├── AssemblyInfo.cs └── WPFAssemblyInfo.cs ├── TextBlockMatrixView.cs ├── TextLineMatrixView.cs ├── WinFormsMatrixView.cs ├── Window1.xaml ├── Window1.xaml.cs └── WpfTextBenchmark.csproj /README: -------------------------------------------------------------------------------- 1 | This benchmark tests several approaches to text rendering in WPF against each other: 2 | 3 | TextBlock: a WPF Canvas with TextBlock elements 4 | FormattedText: using the DrawingContext.DrawText method 5 | TextLine: using the Media.TextFormatting namespace 6 | GlyphRun: using the WPF GlyphRun directly 7 | disadvantage: no automatic font fallback -> no unicode support 8 | 9 | Direct2D: using DirectWrite 10 | WinForms: using the WinForms TextRenderer class 11 | GDI: using GDI via P/Invoke 12 | GDI+: using the System.Drawing.Graphics.DrawString method 13 | disadvantage: accurate measurement of font width seems to be impossible (MeasureString is not good enough) 14 | 15 | The speed of the WPF methods is pretty consistent on most machines, ordered like this: 16 | GlyphRun (fastest) 17 | TextLine 18 | FormattedText 19 | TextBlock (slowest) 20 | 21 | However, even on fast machines like my Core i7, none of the WPF methods provides a fluent animation. 22 | It seems as if most frames (counted by number of OnRender calls) are not drawn to the screen. 23 | 24 | "WinForms" is similarly slow to the WPF approaches (maybe a bit faster on older machines). 25 | Direct2D, GDI and GDI+ all beat WPF hands-down, delivering a fluent animation even on my old notebook with 26 | Intel onboard graphics. The exact order of these seems to depend on the hardware. 27 | 28 | Update: I have since discovered that the WPF DrawingVisual class can be used to 29 | significantly speed up WPF text rendering. 30 | I did not update this benchmark to take advantage of DrawingVisual. 31 | Take a look at AvalonEdit's VisualLineDrawingVisual class for details. 32 | -------------------------------------------------------------------------------- /WpfTextBenchmark.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | # SharpDevelop 4.0.0.6362 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfTextBenchmark", "WpfTextBenchmark\WpfTextBenchmark.csproj", "{121B32DB-5BB9-439F-BF67-1BC697B98EF3}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|x86 = Debug|x86 10 | Release|x86 = Release|x86 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {121B32DB-5BB9-439F-BF67-1BC697B98EF3}.Debug|x86.Build.0 = Debug|x86 16 | {121B32DB-5BB9-439F-BF67-1BC697B98EF3}.Debug|x86.ActiveCfg = Debug|x86 17 | {121B32DB-5BB9-439F-BF67-1BC697B98EF3}.Release|x86.Build.0 = Release|x86 18 | {121B32DB-5BB9-439F-BF67-1BC697B98EF3}.Release|x86.ActiveCfg = Release|x86 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /WpfTextBenchmark/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WpfTextBenchmark/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Data; 4 | using System.Xml; 5 | using System.Configuration; 6 | 7 | namespace WpfTextBenchmark 8 | { 9 | /// 10 | /// Interaction logic for App.xaml 11 | /// 12 | public partial class App : Application 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /WpfTextBenchmark/D2DWrapper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgrunwald/WPF-Text-Rendering-Benchmark/def2a7c36a011836370a2c2743b17fb541793205/WpfTextBenchmark/D2DWrapper.dll -------------------------------------------------------------------------------- /WpfTextBenchmark/Direct2DControl.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Authors: 4 | * Dmitry Kolchev 5 | * 6 | * Copyright (C) 2010 Dmitry Kolchev 7 | * 8 | * This sourcecode is licenced under The GNU Lesser General Public License 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 16 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 19 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | using System; 22 | using System.Collections.Generic; 23 | using System.ComponentModel; 24 | using System.Data; 25 | using System.Diagnostics; 26 | using System.Linq; 27 | using System.Text; 28 | using System.Windows.Forms; 29 | using Managed.Graphics.Direct2D; 30 | using Managed.Graphics.DirectWrite; 31 | using Managed.Graphics.Imaging; 32 | 33 | namespace WpfTextBenchmark 34 | { 35 | public class Direct2DControl : Control 36 | { 37 | private Direct2DFactory _factory; 38 | private DirectWriteFactory _directWriteFactory; 39 | private WicImagingFactory _imagingFactory; 40 | private WindowRenderTarget _renderTarget; 41 | private bool _deviceIndependedResourcesCreated; 42 | 43 | public Direct2DControl() 44 | { 45 | SetStyle( 46 | ControlStyles.AllPaintingInWmPaint | 47 | ControlStyles.Opaque | 48 | ControlStyles.UserPaint, true); 49 | } 50 | 51 | protected override void Dispose(bool disposing) 52 | { 53 | CleanUpDeviceIndependentResources(); 54 | base.Dispose(disposing); 55 | } 56 | 57 | public Direct2DFactory Direct2DFactory 58 | { 59 | get 60 | { 61 | return this._factory; 62 | } 63 | } 64 | 65 | public DirectWriteFactory DirectWriteFactory 66 | { 67 | get 68 | { 69 | if (this._directWriteFactory == null) 70 | { 71 | this._directWriteFactory = DirectWriteFactory.Create(DirectWriteFactoryType.Shared); 72 | } 73 | return this._directWriteFactory; 74 | } 75 | } 76 | 77 | protected WicImagingFactory ImagingFactory 78 | { 79 | get 80 | { 81 | if (this._imagingFactory == null) 82 | { 83 | this._imagingFactory = WicImagingFactory.Create(); 84 | } 85 | return this._imagingFactory; 86 | } 87 | } 88 | 89 | protected WindowRenderTarget RenderTarget 90 | { 91 | get 92 | { 93 | return this._renderTarget; 94 | } 95 | } 96 | 97 | protected override void OnHandleCreated(EventArgs e) 98 | { 99 | CreateDeviceIndependentResources(); 100 | base.OnHandleCreated(e); 101 | } 102 | 103 | private void CreateDeviceIndependentResources() 104 | { 105 | try { 106 | this._factory = Direct2DFactory.CreateFactory(FactoryType.SingleThreaded, DebugLevel.None); 107 | this._renderTarget = this._factory.CreateWindowRenderTarget(this); 108 | OnCreateDeviceIndependentResources(this._factory); 109 | this._deviceIndependedResourcesCreated = true; 110 | } catch (Exception ex) { 111 | Debug.WriteLine(ex.ToString()); 112 | throw; 113 | } 114 | } 115 | 116 | private void CleanUpDeviceIndependentResources() 117 | { 118 | if(this._deviceIndependedResourcesCreated) 119 | OnCleanUpDeviceIndependentResources(); 120 | if (this._imagingFactory != null) 121 | { 122 | this._imagingFactory.Dispose(); 123 | this._imagingFactory = null; 124 | } 125 | if (this._directWriteFactory != null) 126 | { 127 | this._directWriteFactory.Dispose(); 128 | this._directWriteFactory = null; 129 | } 130 | if (this._renderTarget != null) 131 | { 132 | this._renderTarget.Dispose(); 133 | this._renderTarget = null; 134 | } 135 | if (this._factory != null) 136 | { 137 | this._factory.Dispose(); 138 | this._factory = null; 139 | } 140 | } 141 | 142 | protected virtual void OnCleanUpDeviceIndependentResources() 143 | { 144 | } 145 | 146 | private void CleanUpDeviceResources() 147 | { 148 | OnCleanUpDeviceResources(); 149 | } 150 | 151 | protected virtual void OnCleanUpDeviceResources() 152 | { 153 | } 154 | 155 | private void CreateDeviceResources() 156 | { 157 | OnCreateDeviceResources(this._renderTarget); 158 | } 159 | 160 | protected virtual void OnCreateDeviceResources(WindowRenderTarget renderTarget) 161 | { 162 | } 163 | 164 | protected virtual void OnCreateDeviceIndependentResources(Direct2DFactory factory) 165 | { 166 | } 167 | 168 | protected override void OnPaint(PaintEventArgs e) 169 | { 170 | CreateDeviceResources(); 171 | try 172 | { 173 | this._renderTarget.BeginDraw(); 174 | try 175 | { 176 | this._renderTarget.Transform = Matrix3x2.Identity; 177 | this._renderTarget.Clear(Color.FromRGB(this.BackColor.R, this.BackColor.G, this.BackColor.B)); 178 | OnRender(RenderTarget); 179 | } 180 | finally 181 | { 182 | this._renderTarget.EndDraw(); 183 | } 184 | } 185 | finally 186 | { 187 | CleanUpDeviceResources(); 188 | } 189 | } 190 | 191 | public void Render() 192 | { 193 | OnPaint(null); 194 | } 195 | 196 | protected virtual void OnRender(WindowRenderTarget renderTarget) 197 | { 198 | } 199 | 200 | protected override void OnResize(EventArgs e) 201 | { 202 | base.OnResize(e); 203 | if (this.RenderTarget != null) 204 | { 205 | this.RenderTarget.Resize(new SizeU { Width = (uint)ClientSize.Width, Height = (uint)ClientSize.Height }); 206 | Invalidate(); 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /WpfTextBenchmark/Direct2DTextMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Windows.Controls; 21 | 22 | using Managed.Graphics.Direct2D; 23 | using Managed.Graphics.DirectWrite; 24 | 25 | namespace WpfTextBenchmark 26 | { 27 | public class Direct2DTextMatrixView : WinFormsMatrixView 28 | { 29 | D2DView d2d; 30 | 31 | public Direct2DTextMatrixView() 32 | { 33 | host.Child = d2d = new D2DView() { parent = this }; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return "Direct2D"; 39 | } 40 | 41 | System.Windows.Media.Typeface cachedTypeface; 42 | List texts = new List(); 43 | 44 | public override IText AddText(string text, double fontSize, System.Windows.Media.SolidColorBrush brush) 45 | { 46 | if (cachedTypeface == null) { 47 | cachedTypeface = CreateTypeface(); 48 | } 49 | var textFormat = d2d.DirectWriteFactory.CreateTextFormat( 50 | cachedTypeface.FontFamily.Source, 51 | FontWeight.Normal, 52 | FontStyle.Normal, 53 | FontStretch.Normal, 54 | (float)fontSize); 55 | textFormat.TextAlignment = TextAlignment.Leading; 56 | textFormat.ParagraphAlignment = ParagraphAlignment.Near; 57 | 58 | var textLayout = d2d.DirectWriteFactory.CreateTextLayout( 59 | text, 60 | textFormat, 61 | 300, 100); 62 | 63 | MyText myText = new MyText { 64 | parent = this, 65 | textFormat = textFormat, 66 | textLayout = textLayout, 67 | color = Color.FromRGB(brush.Color.R / 255f, brush.Color.G / 255f, brush.Color.B / 255f) 68 | }; 69 | texts.Add(myText); 70 | return myText; 71 | } 72 | 73 | class MyText : IText 74 | { 75 | public Direct2DTextMatrixView parent; 76 | public TextFormat textFormat; 77 | public TextLayout textLayout; 78 | public Color color; 79 | public Brush brush; 80 | public System.Windows.Point Position { get; set; } 81 | 82 | public void Remove() 83 | { 84 | textFormat.Dispose(); 85 | textLayout.Dispose(); 86 | parent.texts.Remove(this); 87 | } 88 | } 89 | 90 | sealed class D2DView : Direct2DControl 91 | { 92 | public Direct2DTextMatrixView parent; 93 | 94 | protected override void OnCreateDeviceIndependentResources(Direct2DFactory factory) 95 | { 96 | base.OnCreateDeviceIndependentResources(factory); 97 | } 98 | 99 | protected override void OnCleanUpDeviceIndependentResources() 100 | { 101 | base.OnCleanUpDeviceIndependentResources(); 102 | } 103 | 104 | protected override void OnCreateDeviceResources(WindowRenderTarget renderTarget) 105 | { 106 | base.OnCreateDeviceResources(renderTarget); 107 | foreach (MyText text in parent.texts) { 108 | text.brush = renderTarget.CreateSolidColorBrush(text.color); 109 | } 110 | } 111 | 112 | protected override void OnCleanUpDeviceResources() 113 | { 114 | base.OnCleanUpDeviceResources(); 115 | foreach (MyText text in parent.texts) { 116 | text.brush.Dispose(); 117 | text.brush = null; 118 | } 119 | parent.OnFrameRendered(EventArgs.Empty); 120 | } 121 | 122 | protected override void OnRender(WindowRenderTarget renderTarget) 123 | { 124 | foreach (MyText text in parent.texts) { 125 | renderTarget.DrawTextLayout(new PointF((float)text.Position.X, (float)text.Position.Y), 126 | text.textLayout, 127 | text.brush, 128 | DrawTextOptions.None); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /WpfTextBenchmark/ExtTextOutMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Drawing; 21 | using System.Runtime.InteropServices; 22 | using System.Windows.Forms; 23 | 24 | namespace WpfTextBenchmark 25 | { 26 | public unsafe class ExtTextOutMatrixView : WinFormsMatrixView 27 | { 28 | TextView view; 29 | 30 | public ExtTextOutMatrixView() 31 | { 32 | host.Child = view = new TextView() { parent = this }; 33 | } 34 | 35 | public override string ToString() 36 | { 37 | return "GDI (ExtTextOut)"; 38 | } 39 | 40 | System.Windows.Media.Typeface cachedTypeface; 41 | List texts = new List(); 42 | 43 | public override IText AddText(string text, double fontSize, System.Windows.Media.SolidColorBrush brush) 44 | { 45 | if (cachedTypeface == null) { 46 | cachedTypeface = CreateTypeface(); 47 | } 48 | 49 | LOGFONT logfont = new LOGFONT(); 50 | logfont.lfFaceName = cachedTypeface.FontFamily.Source; 51 | logfont.lfHeight = -(int)fontSize; 52 | //logfont.lfOutPrecision = 4; 53 | //logfont.lfQuality = 5; 54 | 55 | MyText myText = new MyText { 56 | parent = this, 57 | text = text, 58 | color = brush.Color.R | (brush.Color.G << 8) | (brush.Color.B << 16), 59 | font = CreateFontIndirect(ref logfont) 60 | }; 61 | //myText.brush = new SolidBrush(myText.color); 62 | 63 | texts.Add(myText); 64 | return myText; 65 | } 66 | 67 | [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] 68 | struct GCP_RESULTS 69 | { 70 | public int lStructSize; 71 | public char* lpOutString; 72 | public uint* lpOrder; 73 | public int* lpDx; 74 | public int* lpCaretPos; 75 | public byte* lpClass; 76 | public char* lpGlyphs; 77 | public uint nGlyphs; 78 | public int nMaxFit; 79 | } 80 | 81 | [DllImportAttribute("gdi32.dll", CharSet=CharSet.Unicode)] 82 | static extern int GetCharacterPlacement( 83 | IntPtr hdc, 84 | string lpString, 85 | int nCount, 86 | int nMaxExtent, 87 | ref GCP_RESULTS lpResults, 88 | uint dwFlags 89 | ); 90 | 91 | [DllImportAttribute("gdi32.dll")] 92 | static extern IntPtr CreateFontIndirect(ref LOGFONT lplf); 93 | 94 | [DllImportAttribute("gdi32.dll")] 95 | [return: MarshalAs(UnmanagedType.Bool)] 96 | static extern bool DeleteObject(IntPtr hObject); 97 | 98 | [DllImportAttribute("gdi32.dll")] 99 | static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); 100 | 101 | [DllImportAttribute("gdi32.dll", CharSet=CharSet.Unicode)] 102 | [return: MarshalAs(UnmanagedType.Bool)] 103 | static extern bool ExtTextOut( 104 | IntPtr hdc, 105 | int X, 106 | int Y, 107 | uint fuOptions, 108 | void* lprc, 109 | string lpString, 110 | int cbCount, 111 | int[] lpDx 112 | ); 113 | 114 | const int GCP_REORDER = 2; 115 | 116 | [DllImport("gdi32.dll")] 117 | static extern int SetBkMode(IntPtr hdc, int iBkMode); 118 | 119 | [DllImport("gdi32.dll")] 120 | static extern int SetTextColor(IntPtr hdc, int crColor); 121 | 122 | const int LF_FACESIZE = 32; 123 | 124 | [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] 125 | struct LOGFONT 126 | { 127 | public int lfHeight; 128 | public int lfWidth; 129 | public int lfEscapement; 130 | public int lfOrientation; 131 | public int lfWeight; 132 | public byte lfItalic; 133 | public byte lfUnderline; 134 | public byte lfStrikeOut; 135 | public byte lfCharSet; 136 | public byte lfOutPrecision; 137 | public byte lfClipPrecision; 138 | public byte lfQuality; 139 | public byte lfPitchAndFamily; 140 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] 141 | public string lfFaceName; 142 | } 143 | 144 | class MyText : IText 145 | { 146 | public string text; 147 | public ExtTextOutMatrixView parent; 148 | public int color; 149 | public IntPtr font; 150 | public System.Windows.Point Position { get; set; } 151 | 152 | public void Remove() 153 | { 154 | DeleteObject(font); 155 | parent.texts.Remove(this); 156 | } 157 | } 158 | 159 | sealed class TextView : Control 160 | { 161 | public ExtTextOutMatrixView parent; 162 | 163 | public TextView() 164 | { 165 | this.BackColor = Color.Black; 166 | SetStyle( 167 | ControlStyles.AllPaintingInWmPaint | 168 | ControlStyles.DoubleBuffer | 169 | ControlStyles.Opaque | 170 | ControlStyles.UserPaint, true); 171 | } 172 | 173 | protected override void OnPaint(PaintEventArgs e) 174 | { 175 | e.Graphics.Clear(Color.Black); 176 | IntPtr hdc = e.Graphics.GetHdc(); 177 | try { 178 | SetBkMode(hdc, 1); // TRANSPARENT 179 | foreach (MyText text in parent.texts) { 180 | SetTextColor(hdc, text.color); 181 | SelectObject(hdc, text.font); 182 | ExtTextOut(hdc, (int)text.Position.X, (int)text.Position.Y, 183 | 0, null, text.text, text.text.Length, null); 184 | } 185 | } finally { 186 | e.Graphics.ReleaseHdc(); 187 | } 188 | parent.OnFrameRendered(EventArgs.Empty); 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /WpfTextBenchmark/FormattedTextMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Authors: 4 | * Dmitry Kolchev 5 | * 6 | * Copyright (C) 2010 Daniel Grunwald 7 | * 8 | * This sourcecode is licenced under The GNU Lesser General Public License 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 14 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 16 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 19 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | using System; 23 | using System.Collections.Generic; 24 | using System.Globalization; 25 | using System.Windows; 26 | using System.Windows.Controls; 27 | using System.Windows.Media; 28 | using System.Windows.Threading; 29 | 30 | namespace WpfTextBenchmark 31 | { 32 | /// 33 | /// Description of FormattedTextMatrixView. 34 | /// 35 | public class FormattedTextMatrixView: MatrixView 36 | { 37 | Typeface cachedTypeface; 38 | 39 | public override string ToString() 40 | { 41 | return "WPF FormattedText"; 42 | } 43 | 44 | public override IText AddText(string text, double fontSize, SolidColorBrush brush) 45 | { 46 | if (cachedTypeface == null) { 47 | cachedTypeface = CreateTypeface(); 48 | } 49 | FormattedText f = new FormattedText(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, cachedTypeface, 50 | fontSize, brush, null, TextFormattingMode.Display); 51 | MyText myText = new MyText { line = f, parent = this }; 52 | texts.Add(myText); 53 | return myText; 54 | } 55 | 56 | List texts = new List(); 57 | 58 | protected override void OnRender(DrawingContext drawingContext) 59 | { 60 | foreach (MyText text in texts) { 61 | drawingContext.DrawText(text.line, text.Position); 62 | } 63 | Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { OnFrameRendered(EventArgs.Empty); })); 64 | } 65 | 66 | class MyText : IText 67 | { 68 | public FormattedTextMatrixView parent; 69 | public FormattedText line; 70 | public Point Position { get; set; } 71 | 72 | public void Remove() 73 | { 74 | parent.texts.Remove(this); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /WpfTextBenchmark/GDIMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Drawing; 21 | using System.Windows.Forms; 22 | 23 | namespace WpfTextBenchmark 24 | { 25 | public class GDIMatrixView : WinFormsMatrixView 26 | { 27 | GDITextView view; 28 | 29 | public GDIMatrixView(bool textRenderer) 30 | { 31 | host.Child = view = new GDITextView() { parent = this, textRenderer = textRenderer }; 32 | } 33 | 34 | public override string ToString() 35 | { 36 | return view.textRenderer ? "GDI (TextRenderer)" : "GDI+ (Graphics.DrawString)"; 37 | } 38 | 39 | System.Windows.Media.Typeface cachedTypeface; 40 | List texts = new List(); 41 | 42 | public override IText AddText(string text, double fontSize, System.Windows.Media.SolidColorBrush brush) 43 | { 44 | if (cachedTypeface == null) { 45 | cachedTypeface = CreateTypeface(); 46 | } 47 | 48 | MyText myText = new MyText { 49 | parent = this, 50 | text = text, 51 | color = Color.FromArgb(brush.Color.R, brush.Color.G, brush.Color.B), 52 | font = new Font(cachedTypeface.FontFamily.Source, (float)(fontSize / 96 * 72), FontStyle.Regular), 53 | }; 54 | myText.brush = new SolidBrush(myText.color); 55 | texts.Add(myText); 56 | return myText; 57 | } 58 | 59 | class MyText : IText 60 | { 61 | public string text; 62 | public GDIMatrixView parent; 63 | public Font font; 64 | public Color color; 65 | public SolidBrush brush; 66 | public System.Windows.Point Position { get; set; } 67 | 68 | public void Remove() 69 | { 70 | font.Dispose(); 71 | brush.Dispose(); 72 | parent.texts.Remove(this); 73 | } 74 | } 75 | 76 | sealed class GDITextView : Control 77 | { 78 | public GDIMatrixView parent; 79 | public bool textRenderer; 80 | 81 | public GDITextView() 82 | { 83 | this.BackColor = Color.Black; 84 | SetStyle( 85 | ControlStyles.AllPaintingInWmPaint | 86 | ControlStyles.DoubleBuffer | 87 | ControlStyles.Opaque | 88 | ControlStyles.UserPaint, true); 89 | } 90 | 91 | protected override void OnPaint(PaintEventArgs e) 92 | { 93 | e.Graphics.Clear(Color.Black); 94 | if (textRenderer) { 95 | foreach (MyText text in parent.texts) { 96 | TextRenderer.DrawText(e.Graphics, text.text, text.font, new Point((int)text.Position.X, (int)text.Position.Y), text.color, TextFormatFlags.NoClipping | TextFormatFlags.NoPrefix); 97 | } 98 | } else { 99 | StringFormat f = new StringFormat(StringFormatFlags.NoClip | StringFormatFlags.FitBlackBox); 100 | foreach (MyText text in parent.texts) { 101 | e.Graphics.DrawString(text.text, text.font, text.brush, (float)text.Position.X, (float)text.Position.Y, f); 102 | } 103 | f.Dispose(); 104 | } 105 | parent.OnFrameRendered(EventArgs.Empty); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /WpfTextBenchmark/GlyphMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Globalization; 21 | using System.Windows; 22 | using System.Windows.Controls; 23 | using System.Windows.Markup; 24 | using System.Windows.Media; 25 | using System.Windows.Threading; 26 | 27 | namespace WpfTextBenchmark 28 | { 29 | /// 30 | /// Description of GlyphMatrixView. 31 | /// 32 | public class GlyphMatrixView : MatrixView 33 | { 34 | GlyphTypeface cachedTypeface; 35 | 36 | public override string ToString() 37 | { 38 | return "WPF GlyphRun"; 39 | } 40 | 41 | public override IText AddText(string text, double fontSize, SolidColorBrush brush) 42 | { 43 | if (cachedTypeface == null) { 44 | var t = CreateTypeface(); 45 | if (!t.TryGetGlyphTypeface(out cachedTypeface)) 46 | throw new NotSupportedException(); 47 | } 48 | 49 | ushort[] glyphIndexes = new ushort[text.Length]; 50 | double[] advanceWidths = new double[text.Length]; 51 | 52 | double totalWidth = 0; 53 | for (int n = 0; n < text.Length; n++) { 54 | ushort glyphIndex; 55 | cachedTypeface.CharacterToGlyphMap.TryGetValue(text[n], out glyphIndex); 56 | glyphIndexes[n] = glyphIndex; 57 | double width = cachedTypeface.AdvanceWidths[glyphIndex] * fontSize; 58 | advanceWidths[n] = width; 59 | totalWidth += width; 60 | } 61 | 62 | GlyphRun run = new GlyphRun(cachedTypeface, 63 | bidiLevel: 0, 64 | isSideways: false, 65 | renderingEmSize: fontSize, 66 | glyphIndices: glyphIndexes, 67 | baselineOrigin: new Point(0, Math.Round(cachedTypeface.Baseline * fontSize)), 68 | advanceWidths: advanceWidths, 69 | glyphOffsets: null, 70 | characters: null, 71 | deviceFontName: null, 72 | clusterMap: null, 73 | caretStops: null, 74 | language: null); 75 | MyText myText = new MyText { run = run, parent = this, brush = brush }; 76 | texts.Add(myText); 77 | return myText; 78 | } 79 | 80 | List texts = new List(); 81 | 82 | protected override void OnRender(DrawingContext drawingContext) 83 | { 84 | foreach (MyText text in texts) { 85 | drawingContext.PushTransform(new TranslateTransform(text.Position.X, text.Position.Y)); 86 | drawingContext.DrawGlyphRun(text.brush, text.run); 87 | drawingContext.Pop(); 88 | } 89 | Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { OnFrameRendered(EventArgs.Empty); })); 90 | } 91 | 92 | class MyText : IText 93 | { 94 | public GlyphMatrixView parent; 95 | public GlyphRun run; 96 | public Brush brush; 97 | public Point Position { get; set; } 98 | 99 | public void Remove() 100 | { 101 | parent.texts.Remove(this); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /WpfTextBenchmark/MatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Windows; 20 | using System.Windows.Controls; 21 | using System.Windows.Media; 22 | 23 | namespace WpfTextBenchmark 24 | { 25 | public interface IText 26 | { 27 | Point Position { get; set; } 28 | void Remove(); 29 | } 30 | 31 | public abstract class MatrixView : FrameworkElement 32 | { 33 | public MatrixView() 34 | { 35 | this.ClipToBounds = true; 36 | } 37 | 38 | public abstract IText AddText(string text, double fontSize, SolidColorBrush brush); 39 | 40 | public event EventHandler FrameRendered; 41 | 42 | protected virtual void OnFrameRendered(EventArgs e) 43 | { 44 | if (FrameRendered != null) { 45 | FrameRendered(this, e); 46 | } 47 | } 48 | 49 | protected Typeface CreateTypeface() 50 | { 51 | return new Typeface((FontFamily)GetValue(TextBlock.FontFamilyProperty), 52 | (FontStyle)GetValue(TextBlock.FontStyleProperty), 53 | (FontWeight)GetValue(TextBlock.FontWeightProperty), 54 | (FontStretch)GetValue(TextBlock.FontStretchProperty)); 55 | } 56 | 57 | public virtual void Refresh() 58 | { 59 | InvalidateVisual(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /WpfTextBenchmark/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region Using directives 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | #endregion 8 | 9 | // General Information about an assembly is controlled through the following 10 | // set of attributes. Change these attribute values to modify the information 11 | // associated with an assembly. 12 | [assembly: AssemblyTitle("WpfTextBenchmark")] 13 | [assembly: AssemblyDescription("")] 14 | [assembly: AssemblyConfiguration("")] 15 | [assembly: AssemblyCompany("")] 16 | [assembly: AssemblyProduct("WpfTextBenchmark")] 17 | [assembly: AssemblyCopyright("Copyright 2010")] 18 | [assembly: AssemblyTrademark("")] 19 | [assembly: AssemblyCulture("")] 20 | 21 | // This sets the default COM visibility of types in the assembly to invisible. 22 | // If you need to expose a type to COM, use [ComVisible(true)] on that type. 23 | [assembly: ComVisible(false)] 24 | 25 | // The assembly version has following format : 26 | // 27 | // Major.Minor.Build.Revision 28 | // 29 | // You can specify all the values or you can use the default the Revision and 30 | // Build Numbers by using the '*' as shown below: 31 | [assembly: AssemblyVersion("1.0.*")] 32 | -------------------------------------------------------------------------------- /WpfTextBenchmark/Properties/WPFAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region Using directives 2 | 3 | using System.Resources; 4 | using System.Windows; 5 | 6 | #endregion 7 | 8 | 9 | 10 | //In order to begin building localizable applications, set 11 | //CultureYouAreCodingWith in your .csproj file 12 | //inside a . For example, if you are using US english 13 | //in your source files, set the to en-US. Then uncomment 14 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 15 | //the line below to match the UICulture setting in the project file. 16 | 17 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 18 | 19 | 20 | [assembly: ThemeInfo( 21 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 22 | //(used if a resource is not found in the page, 23 | // or application resource dictionaries) 24 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 25 | //(used if a resource is not found in the page, 26 | // app, or any theme specific resource dictionaries) 27 | )] 28 | -------------------------------------------------------------------------------- /WpfTextBenchmark/TextBlockMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Windows; 20 | using System.Windows.Controls; 21 | using System.Windows.Media; 22 | using System.Windows.Threading; 23 | 24 | namespace WpfTextBenchmark 25 | { 26 | public class TextBlockMatrixView : MatrixView 27 | { 28 | protected readonly Canvas host = new Canvas(); 29 | 30 | public TextBlockMatrixView() 31 | { 32 | this.AddVisualChild(host); 33 | this.AddLogicalChild(host); 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return "Canvas + TextBlocks"; 39 | } 40 | 41 | protected override int VisualChildrenCount { 42 | get { return 1; } 43 | } 44 | 45 | protected override Visual GetVisualChild(int index) 46 | { 47 | return host; 48 | } 49 | 50 | protected override Size MeasureOverride(Size availableSize) 51 | { 52 | host.Measure(availableSize); 53 | return host.DesiredSize; 54 | } 55 | 56 | protected override Size ArrangeOverride(Size finalSize) 57 | { 58 | host.Arrange(new Rect(new Point(0, 0), finalSize)); 59 | return finalSize; 60 | } 61 | 62 | public override void Refresh() 63 | { 64 | Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { OnFrameRendered(EventArgs.Empty); })); 65 | } 66 | 67 | public override IText AddText(string text, double fontSize, SolidColorBrush brush) 68 | { 69 | MyText myText = new MyText(); 70 | myText.Text = text; 71 | myText.FontSize = fontSize; 72 | myText.Foreground = brush; 73 | myText.parentCanvas = host; 74 | host.Children.Add(myText); 75 | return myText; 76 | } 77 | 78 | sealed class MyText : TextBlock, IText 79 | { 80 | public Canvas parentCanvas; 81 | 82 | public Point Position { 83 | get { 84 | return new Point(Canvas.GetLeft(this), Canvas.GetTop(this)); 85 | } 86 | set { 87 | Canvas.SetLeft(this, value.X); 88 | Canvas.SetTop(this, value.Y); 89 | } 90 | } 91 | 92 | public void Remove() 93 | { 94 | parentCanvas.Children.Remove(this); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /WpfTextBenchmark/TextLineMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Globalization; 21 | using System.Windows; 22 | using System.Windows.Controls; 23 | using System.Windows.Media; 24 | using System.Windows.Media.TextFormatting; 25 | using System.Windows.Threading; 26 | 27 | namespace WpfTextBenchmark 28 | { 29 | public class TextLineMatrixView : MatrixView 30 | { 31 | TextFormatter formatter = TextFormatter.Create(TextFormattingMode.Display); 32 | Typeface cachedTypeface; 33 | 34 | public override string ToString() 35 | { 36 | return "WPF TextLine"; 37 | } 38 | 39 | public override IText AddText(string text, double fontSize, SolidColorBrush brush) 40 | { 41 | if (cachedTypeface == null) { 42 | cachedTypeface = CreateTypeface(); 43 | } 44 | GlobalTextRunProperties p = new GlobalTextRunProperties { 45 | typeface = cachedTypeface, 46 | fontRenderingEmSize = fontSize, 47 | foregroundBrush = brush, 48 | cultureInfo = CultureInfo.CurrentCulture 49 | }; 50 | MyTextSource myTextSource = new MyTextSource { text = text, textRunProperties = p }; 51 | TextLine line = formatter.FormatLine(myTextSource, 0, 500, new MyTextParagraphProperties {defaultTextRunProperties = p}, null); 52 | MyText myText = new MyText { line = line, parent = this }; 53 | texts.Add(myText); 54 | return myText; 55 | } 56 | 57 | List texts = new List(); 58 | 59 | protected override void OnRender(DrawingContext drawingContext) 60 | { 61 | foreach (MyText text in texts) { 62 | text.line.Draw(drawingContext, text.Position, InvertAxes.None); 63 | } 64 | Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(delegate { OnFrameRendered(EventArgs.Empty); })); 65 | } 66 | 67 | class MyText : IText 68 | { 69 | public TextLineMatrixView parent; 70 | public TextLine line; 71 | public Point Position { get; set; } 72 | 73 | public void Remove() 74 | { 75 | line.Dispose(); 76 | parent.texts.Remove(this); 77 | } 78 | } 79 | 80 | class MyTextSource : TextSource 81 | { 82 | public string text; 83 | public TextRunProperties textRunProperties; 84 | 85 | public override TextRun GetTextRun(int textSourceCharacterIndex) 86 | { 87 | if (textSourceCharacterIndex == 0) 88 | return new TextCharacters(text, 0, text.Length, textRunProperties); 89 | return new TextEndOfParagraph(1); 90 | } 91 | 92 | public override TextSpan GetPrecedingText(int textSourceCharacterIndexLimit) 93 | { 94 | throw new NotImplementedException(); 95 | } 96 | 97 | public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int textSourceCharacterIndex) 98 | { 99 | throw new NotImplementedException(); 100 | } 101 | } 102 | 103 | sealed class GlobalTextRunProperties : TextRunProperties 104 | { 105 | internal Typeface typeface; 106 | internal double fontRenderingEmSize; 107 | internal Brush foregroundBrush; 108 | internal Brush backgroundBrush = null; 109 | internal System.Globalization.CultureInfo cultureInfo; 110 | 111 | public override Typeface Typeface { get { return typeface; } } 112 | public override double FontRenderingEmSize { get { return fontRenderingEmSize; } } 113 | public override double FontHintingEmSize { get { return fontRenderingEmSize; } } 114 | public override TextDecorationCollection TextDecorations { get { return null; } } 115 | public override Brush ForegroundBrush { get { return foregroundBrush; } } 116 | public override Brush BackgroundBrush { get { return backgroundBrush; } } 117 | public override System.Globalization.CultureInfo CultureInfo { get { return cultureInfo; } } 118 | public override TextEffectCollection TextEffects { get { return null; } } 119 | } 120 | 121 | class MyTextParagraphProperties : TextParagraphProperties 122 | { 123 | internal TextRunProperties defaultTextRunProperties; 124 | internal TextWrapping textWrapping = TextWrapping.Wrap; 125 | internal double tabSize = 40; 126 | 127 | public override double DefaultIncrementalTab { 128 | get { return tabSize; } 129 | } 130 | 131 | public override FlowDirection FlowDirection { get { return FlowDirection.LeftToRight; } } 132 | public override TextAlignment TextAlignment { get { return TextAlignment.Left; } } 133 | public override double LineHeight { get { return double.NaN; } } 134 | public override bool FirstLineInParagraph { get { return false; } } 135 | public override TextRunProperties DefaultTextRunProperties { get { return defaultTextRunProperties; } } 136 | public override TextWrapping TextWrapping { get { return textWrapping; } } 137 | public override TextMarkerProperties TextMarkerProperties { get { return null; } } 138 | public override double Indent { get { return 0; } } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /WpfTextBenchmark/WinFormsMatrixView.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Windows; 20 | using System.Windows.Forms.Integration; 21 | using System.Windows.Media; 22 | 23 | namespace WpfTextBenchmark 24 | { 25 | public abstract class WinFormsMatrixView : MatrixView 26 | { 27 | protected readonly WindowsFormsHost host = new WindowsFormsHost(); 28 | 29 | public WinFormsMatrixView() 30 | { 31 | this.AddVisualChild(host); 32 | this.AddLogicalChild(host); 33 | } 34 | 35 | protected override int VisualChildrenCount { 36 | get { return 1; } 37 | } 38 | 39 | protected override Visual GetVisualChild(int index) 40 | { 41 | return host; 42 | } 43 | 44 | protected override Size MeasureOverride(Size availableSize) 45 | { 46 | host.Measure(availableSize); 47 | return host.DesiredSize; 48 | } 49 | 50 | protected override Size ArrangeOverride(Size finalSize) 51 | { 52 | host.Arrange(new Rect(new Point(0, 0), finalSize)); 53 | return finalSize; 54 | } 55 | 56 | public override void Refresh() 57 | { 58 | host.Child.Invalidate(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /WpfTextBenchmark/Window1.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Pause 19 | Unicode 20 | Keep 21 | Words 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /WpfTextBenchmark/Window1.xaml.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Daniel Grunwald 3 | * 4 | * This sourcecode is licenced under The GNU Lesser General Public License 5 | * 6 | * The above copyright notice and this permission notice shall be included in 7 | * all copies or substantial portions of the Software. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 11 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 12 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 13 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 15 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Diagnostics; 21 | using System.Linq; 22 | using System.Text; 23 | using System.Windows; 24 | using System.Windows.Controls; 25 | using System.Windows.Data; 26 | using System.Windows.Documents; 27 | using System.Windows.Input; 28 | using System.Windows.Media; 29 | using System.Windows.Threading; 30 | 31 | namespace WpfTextBenchmark 32 | { 33 | /// 34 | /// Interaction logic for Window1.xaml 35 | /// 36 | public partial class Window1 : Window 37 | { 38 | MatrixView v ; 39 | Random rnd = new Random(1337); 40 | const int width = 950; 41 | const int height = 350; 42 | List texts = new List(); 43 | IText framerateText; 44 | string[] possibleTexts; 45 | 46 | public Window1() 47 | { 48 | possibleTexts = Enumerable.Range(0, char.MaxValue).Select(c => (char)c).Where(char.IsLetterOrDigit).Select(c => c.ToString()).ToArray(); 49 | InitializeComponent(); 50 | Direct2DButton.IsEnabled = Environment.OSVersion.Version.Major >= 6; 51 | } 52 | 53 | void InitView() 54 | { 55 | this.Title = "Text Benchmark"; 56 | Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action( 57 | delegate { 58 | if (v != null) 59 | this.Title = "Text Benchmark - " + v.ToString(); 60 | })); 61 | framerateText = null; 62 | stopwatch = null; 63 | texts.Clear(); 64 | rnd = new Random(1337); 65 | for (int i = 0; i < (int)textCount.Value; i++) { 66 | texts.Add(NewRandomText()); 67 | texts[i].Position = new Point(rnd.Next(0, width), rnd.Next(-50, height)); 68 | } 69 | v.Width = width; 70 | v.Height = height; 71 | scroll.Content = v; 72 | frameCounter = 0; 73 | lastFrame.Restart(); 74 | v.FrameRendered += new EventHandler(v_FrameRendered); 75 | Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(NextFrame)); 76 | } 77 | 78 | int frameCounter; 79 | 80 | void v_FrameRendered(object sender, EventArgs e) 81 | { 82 | frameCounter++; 83 | NextFrame(); 84 | } 85 | 86 | string RandomCharacter() 87 | { 88 | if (unicode.IsChecked == true) 89 | return possibleTexts[rnd.NextDouble() < 0.2 ? rnd.Next(possibleTexts.Length) : rnd.Next(100)]; 90 | else 91 | return possibleTexts[rnd.Next(26*2+10)]; 92 | } 93 | 94 | IText NewRandomText() 95 | { 96 | string s; 97 | if (words.IsChecked == true) { 98 | int length = rnd.Next(1, 10); 99 | s = string.Empty; 100 | for (int i = 0; i < length; i++) 101 | s += RandomCharacter(); 102 | } else { 103 | s = RandomCharacter(); 104 | } 105 | var brush = new SolidColorBrush(Color.FromRgb((byte)rnd.Next(0,200),(byte)rnd.Next(100,255),(byte)rnd.Next(0,200))); 106 | brush.Freeze(); 107 | IText text = v.AddText(s, 108 | rnd.Next(10, 16), 109 | brush 110 | ); 111 | return text; 112 | } 113 | 114 | Stopwatch stopwatch; 115 | Stopwatch lastFrame = Stopwatch.StartNew(); 116 | 117 | void NextFrame() 118 | { 119 | if (stopped.IsChecked == false && v != null) { 120 | if (stopwatch == null) 121 | stopwatch = Stopwatch.StartNew(); 122 | if (stopwatch.ElapsedMilliseconds > 1000) { 123 | if (framerateText != null) framerateText.Remove(); 124 | double framerate = frameCounter / stopwatch.Elapsed.TotalSeconds; 125 | framerateText = v.AddText(framerate.ToString("f1"), 16, Brushes.White); 126 | stopwatch.Restart(); 127 | frameCounter = 0; 128 | } 129 | long elapsed = lastFrame.ElapsedMilliseconds; 130 | lastFrame.Restart(); 131 | for (int i = 0; i < texts.Count; i++) { 132 | texts[i].Position += new Vector(0, elapsed / 2f); 133 | if (texts[i].Position.Y > height) { 134 | double oldHeight = texts[i].Position.Y; 135 | if (keepEverything.IsChecked == false) { 136 | texts[i].Remove(); 137 | texts[i] = NewRandomText(); 138 | } 139 | texts[i].Position = new Point(rnd.Next(0,width), oldHeight - height - 50); 140 | } 141 | } 142 | v.Refresh(); 143 | } 144 | } 145 | 146 | void GlyphRunButton_Click(object sender, RoutedEventArgs e) 147 | { 148 | v = new GlyphMatrixView(); 149 | InitView(); 150 | } 151 | 152 | void TextLineButton_Click(object sender, RoutedEventArgs e) 153 | { 154 | v = new TextLineMatrixView(); 155 | InitView(); 156 | } 157 | 158 | void FormattedTextButton_Click(object sender, RoutedEventArgs e) 159 | { 160 | v = new FormattedTextMatrixView(); 161 | InitView(); 162 | } 163 | 164 | void TextBlockButton_Click(object sender, RoutedEventArgs e) 165 | { 166 | v = new TextBlockMatrixView(); 167 | InitView(); 168 | } 169 | 170 | void Direct2DButton_Click(object sender, RoutedEventArgs e) 171 | { 172 | v = new Direct2DTextMatrixView(); 173 | InitView(); 174 | } 175 | 176 | void GDIButton_Click(object sender, RoutedEventArgs e) 177 | { 178 | v = new GDIMatrixView(true); 179 | InitView(); 180 | } 181 | 182 | void GDIPlusButton_Click(object sender, RoutedEventArgs e) 183 | { 184 | v = new GDIMatrixView(false); 185 | InitView(); 186 | } 187 | 188 | void ExtTextOutButton_Click(object sender, RoutedEventArgs e) 189 | { 190 | v = new ExtTextOutMatrixView(); 191 | InitView(); 192 | } 193 | 194 | void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) 195 | { 196 | if (v == null) 197 | return; 198 | while (texts.Count < (int)e.NewValue) { 199 | texts.Add(NewRandomText()); 200 | texts[texts.Count-1].Position = new Point(rnd.Next(0, width), rnd.Next(-50, height)); 201 | } 202 | while (texts.Count > (int)e.NewValue) { 203 | texts[texts.Count-1].Remove(); 204 | texts.RemoveAt(texts.Count-1); 205 | } 206 | v.Refresh(); 207 | } 208 | 209 | void Stopped_Unchecked(object sender, RoutedEventArgs e) 210 | { 211 | NextFrame(); 212 | } 213 | } 214 | } -------------------------------------------------------------------------------- /WpfTextBenchmark/WpfTextBenchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {121B32DB-5BB9-439F-BF67-1BC697B98EF3} 5 | Debug 6 | x86 7 | WinExe 8 | WpfTextBenchmark 9 | WpfTextBenchmark 10 | v4.0 11 | Properties 12 | True 13 | False 14 | 4 15 | false 16 | 17 | 18 | bin\Debug\ 19 | true 20 | Full 21 | False 22 | True 23 | DEBUG;TRACE 24 | 25 | 26 | bin\Release\ 27 | False 28 | None 29 | True 30 | False 31 | TRACE 32 | 33 | 34 | False 35 | Auto 36 | 4194304 37 | AnyCPU 38 | 4096 39 | 40 | 41 | x86 42 | 43 | 44 | 45 | D2DWrapper.dll 46 | 47 | 48 | 3.0 49 | 50 | 51 | 3.0 52 | 53 | 54 | 55 | 3.5 56 | 57 | 58 | 59 | 3.5 60 | 61 | 62 | 63 | 64 | 4.0 65 | 66 | 67 | 68 | 3.5 69 | 70 | 71 | 3.0 72 | 73 | 74 | 3.0 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Code 83 | App.xaml 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Code 98 | Window1.xaml 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | --------------------------------------------------------------------------------