Recently I've been trying the rich text sample code in the WPF documentation, the following custom control is something I come up with when playing with the FormattedText class:
using System;
using System.Xml;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Markup;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows.Documents;
using Sheva.Internal;
namespace Sheva.Windows.Controls
{
public class OutlinedText : FrameworkElement
{
private FormattedText formattedText;
public static DependencyProperty FillProperty;
public static DependencyProperty StrokeProperty;
public static DependencyProperty StrokeThicknessProperty;
public static DependencyProperty FontFamilyProperty;
public static DependencyProperty FontSizeProperty;
public static DependencyProperty FontStretchProperty;
public static DependencyProperty FontStyleProperty;
public static DependencyProperty FontWeightProperty;
public static DependencyProperty RenderHighlightProperty;
public static DependencyProperty TextProperty;
public static DependencyProperty TextGeometryProperty;
public static DependencyProperty TextHighlightGeometryProperty;
private static DependencyPropertyKey TextGeometryPropertyKey;
public static DependencyPropertyKey TextHighlightGeometryPropertyKey;
static OutlinedText()
{
FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedText),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedText),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(Double),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
0.0d,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
FontFamilyProperty = DependencyProperty.Register(
"FontFamily",
typeof(FontFamily),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
SystemFonts.MessageFontFamily,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(IsValidFontFamily));
FontStyleProperty = DependencyProperty.Register(
"FontStyle",
typeof(FontStyle),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
SystemFonts.MessageFontStyle,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
FontWeightProperty = DependencyProperty.Register(
"FontWeight",
typeof(FontWeight),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
SystemFonts.MessageFontWeight,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
FontStretchProperty = DependencyProperty.Register(
"FontStretch",
typeof(FontStretch),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
FontStretches.Normal,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
FontSizeProperty = DependencyProperty.Register(
"FontSize",
typeof(Double),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
SystemFonts.MessageFontSize,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
TextProperty = DependencyProperty.Register(
"Text",
typeof(String),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
String.Empty,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
RenderHighlightProperty = DependencyProperty.Register(
"RenderHighlight",
typeof(Boolean),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
BooleanBoxes.FalseBox,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
TextGeometryPropertyKey = DependencyProperty.RegisterReadOnly(
"TextGeometry",
typeof(Geometry),
typeof(OutlinedText),
new FrameworkPropertyMetadata(null));
TextGeometryProperty = TextGeometryPropertyKey.DependencyProperty;
TextHighlightGeometryPropertyKey = DependencyProperty.RegisterReadOnly(
"TextHighlightGeometry",
typeof(Geometry),
typeof(OutlinedText),
new FrameworkPropertyMetadata(null));
TextHighlightGeometryProperty = TextHighlightGeometryPropertyKey.DependencyProperty;
}
public Brush Fill
{
get { return (Brush)base.GetValue(FillProperty); }
set { base.SetValue(FillProperty, value); }
}
public Brush Stroke
{
get { return (Brush)base.GetValue(StrokeProperty); }
set { base.SetValue(StrokeProperty, value); }
}
public Double StrokeThickness
{
get { return (Double)base.GetValue(StrokeThicknessProperty); }
set { base.SetValue(StrokeThicknessProperty, value); }
}
public FontFamily FontFamily
{
get { return (FontFamily)base.GetValue(FontFamilyProperty); }
set { base.SetValue(FontFamilyProperty, value); }
}
public Double FontSize
{
get { return (Double)base.GetValue(FontSizeProperty); }
set { base.SetValue(FontSizeProperty, value); }
}
public FontStyle FontStyle
{
get { return (FontStyle)base.GetValue(FontStyleProperty); }
set { base.SetValue(FontStyleProperty, value); }
}
public FontStretch FontStretch
{
get { return (FontStretch)base.GetValue(FontStretchProperty); }
set { base.SetValue(FontStretchProperty, value); }
}
public FontWeight FontWeight
{
get { return (FontWeight)base.GetValue(FontWeightProperty); }
set { base.SetValue(FontWeightProperty, value); }
}
public String Text
{
get { return (String)base.GetValue(TextProperty); }
set { base.SetValue(TextProperty, value); }
}
public Boolean RenderHighlight
{
get { return (Boolean)base.GetValue(RenderHighlightProperty); }
set { base.SetValue(RenderHighlightProperty, BooleanBoxes.Box(value)); }
}
public Geometry TextGeometry
{
get { return (Geometry)base.GetValue(TextGeometryProperty); }
}
public Geometry TextHighlightGeometry
{
get { return (Geometry)base.GetValue(TextHighlightGeometryProperty); }
}
protected override Size MeasureOverride(Size availableSize)
{
EnsureTextGeometry();
Size geometrySize = GetNaturalSize();
Double width = availableSize.Width;
Double Height = availableSize.Height;
if (Double.IsInfinity(width) && Double.IsInfinity(Height))
{
return geometrySize;
}
if (Double.IsInfinity(width))
{
width = geometrySize.Width;
}
if (Double.IsInfinity(Height))
{
Height = geometrySize.Height;
}
if (width > geometrySize.Width && Height > geometrySize.Height)
{
return geometrySize;
}
return new Size(width, Height);
}
protected override Size ArrangeOverride(Size finalSize)
{
AdjustTextGeometry(finalSize);
return finalSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
if (RenderHighlight)
{
drawingContext.DrawGeometry(Fill, GetPen(), TextHighlightGeometry);
}
drawingContext.DrawGeometry(Fill, GetPen(), TextGeometry);
}
protected virtual void AdjustTextGeometry(Size arrangedSize)
{
Size geometrySize = GetNaturalSize();
Double aspectRatio = geometrySize.Width / geometrySize.Height;
Double scaleX = arrangedSize.Width / geometrySize.Width;
Double scaleY = arrangedSize.Height / geometrySize.Height;
ScaleTransform scaleTransform = new ScaleTransform(scaleX, scaleY, 0, 0);
TextGeometry.Transform = scaleTransform;
if (RenderHighlight)
{
TextHighlightGeometry.Transform = scaleTransform;
}
}
protected virtual void EnsureTextGeometry()
{
FlowDirection flowDirection = Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
formattedText = new FormattedText(
Text,
Thread.CurrentThread.CurrentUICulture,
flowDirection,
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch, new FontFamily("Segeo UI")),
FontSize,
Brushes.Black);
base.SetValue(TextGeometryPropertyKey, formattedText.BuildGeometry(new Point(0, 0)));
if (RenderHighlight)
{
base.SetValue(TextHighlightGeometryPropertyKey, formattedText.BuildHighlightGeometry(new Point(0, 0)));
}
}
private static Boolean IsValidFontFamily(Object value)
{
FontFamily fontFamily = value as FontFamily;
return (fontFamily != null);
}
private Size GetNaturalSize()
{
Geometry definingGeometry = RenderHighlight ? TextHighlightGeometry : TextGeometry;
Pen pen = this.GetPen();
Rect rect = definingGeometry.GetRenderBounds(pen);
return new Size(Math.Max(rect.Right, 0), Math.Max(rect.Bottom, 0));
}
private Pen GetPen()
{
return new Pen(this.Stroke, this.StrokeThickness);
}
}
}
And here is a little programme which demonstrates how to use this custom control:
<Window x:Class="OutlinedTextDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Sheva.Windows.Controls"
Title="OutlinedTextDemo"
>
<DockPanel>
<CheckBox DockPanel.Dock="Top" Content="Render Highlight" Name="checkBox"/>
<cc:OutlinedText
Margin="5"
Text="OutlinedText"
FontFamily="Vineta BT"
FontSize="108"
FontWeight="Bold"
StrokeThickness="4"
Fill="Gold"
Stroke="Green"
RenderHighlight="{Binding Path=IsChecked, ElementName=checkBox}"/>
</DockPanel>
</Window>
After compiling and running the above code, you should see something like the following: