WPF Text(GraphicsPath)→Path変換

WPFでは所望の形にElementを切り出せますが、System.Windows.Shapes.Pathを自作するのはちょっと苦労。例えば文字列のPathを取得しようと思うと簡単にはできません、System.Drawing.Drawing2D.GeometryPathならばStringから直接変換できるわけで、今回はSystem.Drawing.Drawing2D.GeometryPathからSystem.Windos.Shapes.Pathを変換してみようというトライ。

これ地味に面倒で、いくつか関連サイトあるけどどこかごまかしているような。。

System.Windows.Shapes.Pathを作成するとGeometryができるので、それをCanvas.Clipに設定することで切り抜いてやろうと思います。確認用UIはこんな感じ。

 MainWindow.xaml

<Window x:Class="WpfTextClip.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:me="clr-namespace:WpfTextClip"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <me:StringToPathConverter x:Key="strPathConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="7*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBox x:Name="textBox" Grid.Row="1" TextWrapping="Wrap" Text="TextBox"/>
        <Canvas x:Name="targetCanvas" Grid.ColumnSpan="2" Background="Black"/>
        <Button x:Name="convertbutton" Content="Convert" Grid.Column="1" Grid.Row="1" Click="convertbutton_Click"/>
    </Grid>
</Window>

mainwindow

Mainwindow.xaml.cs

namespace WpfTextClip
{
    using System.Windows;
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void convertbutton_Click(object sender, RoutedEventArgs e)
        {
            targetCanvas.Clip = StringToPathConverter.ConvertToPath(textBox.Text).Data;
        }
    }
}

Convertボタンを押すと、TextBoxに指定された文字列で上のCanvasをClipしてやる構図。ということで本題であるGeometryPathからPathを作成するのはこちら。

 StringToPathConverter.cs

namespace WpfTextClip
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Data;
    using System.Windows.Media;
    using System.Windows.Shapes;

    public class StringToPathConverter
    {
        public static System.Windows.Shapes.Path ConvertToPath2(string text)
        {
            // string -> GraphicsPath
            var basePath = new System.Drawing.Drawing2D.GraphicsPath();
            var fontFamily = new System.Drawing.FontFamily("MS ゴシック");
            basePath.AddString(text, fontFamily, (int)System.Drawing.FontStyle.Regular,
                150, System.Drawing.Point.Empty, System.Drawing.StringFormat.GenericDefault);

            // GraphicsPath -> Path
            var pathGeometry = new PathGeometry();
            PathFigure pathFigure = null;

            for (int i = 0; i < basePath.PointCount; i++)
            {
                System.Drawing.Drawing2D.PathPointType pathtype =
                    ((System.Drawing.Drawing2D.PathPointType)basePath.PathTypes[i]) & System.Drawing.Drawing2D.PathPointType.PathTypeMask;
                System.Drawing.Drawing2D.PathPointType control =
                    ((System.Drawing.Drawing2D.PathPointType)basePath.PathTypes[i]) & System.Drawing.Drawing2D.PathPointType.CloseSubpath;

                switch (pathtype)
                {
                    case System.Drawing.Drawing2D.PathPointType.Start:
                        pathFigure = new PathFigure();
                        pathFigure.StartPoint = new System.Windows.Point(basePath.PathPoints[i].X,
                                                                         basePath.PathPoints[i].Y);
                        break;
                    case System.Drawing.Drawing2D.PathPointType.Line:
                        pathFigure.Segments.Add(new LineSegment(new System.Windows.Point(basePath.PathPoints[i].X,
                                                              basePath.PathPoints[i].Y), true));
                        break;
                    case System.Drawing.Drawing2D.PathPointType.Bezier:
                        if (basePath.PointCount-1 < (i + 2)){
                            // index out of range.
                            continue;
                        }
                        var ii = ((System.Drawing.Drawing2D.PathPointType)basePath.PathTypes[i+1]) & System.Drawing.Drawing2D.PathPointType.PathTypeMask;
                        var iii = ((System.Drawing.Drawing2D.PathPointType)basePath.PathTypes[i+2]) & System.Drawing.Drawing2D.PathPointType.PathTypeMask;
                        if (ii == System.Drawing.Drawing2D.PathPointType.Bezier &&
                            iii == System.Drawing.Drawing2D.PathPointType.Bezier)
                        {
                            pathFigure.Segments.Add(new BezierSegment(
                                new System.Windows.Point(basePath.PathPoints[i].X, basePath.PathPoints[i].Y),
                                new System.Windows.Point(basePath.PathPoints[i+1].X, basePath.PathPoints[i+1].Y),
                                new System.Windows.Point(basePath.PathPoints[i+2].X, basePath.PathPoints[i+2].Y),
                                true));
                        }
                        break;
                }
                switch (control)
                {
                    case System.Drawing.Drawing2D.PathPointType.DashMode:
                    // not supported.
                    case System.Drawing.Drawing2D.PathPointType.PathMarker:
                        // ignore.
                        break;
                    case System.Drawing.Drawing2D.PathPointType.CloseSubpath:
                        pathGeometry.Figures.Add(pathFigure);
                        break;
                }
            }
            Path path = new Path();
            path.Data = pathGeometry;
            return path;
        }

    }
}

フー。FontFamilyやサイズは適当に固定値にしていますが必要に応じて値を変更してくださいませ。これを実行するとこんな感じになります。

runmainwindow

わかりにくいですがCanvasそのものがClipされています。