#region Using ステートメント using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Graphics.PackedVector; using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Xml; using Color = Microsoft.Xna.Framework.Color; #endregion namespace WpfFontPipeline { /// /// WPFフォントプロセッサーで使用するテクスチャフォーマット /// public enum WpfTextureFormat { Auto, // 自動:単色の場合にはDXT3、アウトライン使用でBgra444、 // グラデーション使用でColorとフォーマットを切り替える Color, Bgra4444 } /// /// アウトライン描画方法 /// public enum OutlineStroke { StrokeOverFill, // 文字本体描画の後にアウトラインを描画する FillOverStroke, // アウトラインを描画した後に文字本体描画する StrokeOnly // アウトラインのみを描画する } /// /// WPF文字描画を使ったフォントプロセッサー /// [ContentProcessor(DisplayName = "WPF フォントプロセッサー")] public class WpfFontDescriptionProcessor : ContentProcessor { #region プロパティ /// /// 半角英数時を生成文字として追加するか /// [DisplayName("追加文字 ASCII")] [Description("フォント生成時に半角英数字、記号を追加します。")] [DefaultValue(true)] public bool HasAsciiCharacters { get; set; } /// /// 全角英数時を生成文字として追加するか /// [DisplayName("追加文字 全角英数字")] [Description("フォント生成時に全角英数字を追加します。")] [DefaultValue(false)] public bool HasZenkakuLatinLetters { get; set; } /// /// 特殊記号文字を追加するか /// [DisplayName("追加文字 記号")] [Description("フォント生成時に\"「」…、\"等の記号を追加します。")] [DefaultValue(false)] public bool HasSpecialCharacters { get; set; } /// /// ひらがな文字を追加するか /// [DisplayName("追加文字 ひらがな")] [Description("フォント生成時にひらがな文字を追加します。")] [DefaultValue(false)] public bool HasHiragana { get; set; } /// /// カタカナ文字を追加するか /// [DisplayName("追加文字 カタカナ")] [Description("フォント生成時にカタカナ文字を追加します。")] [DefaultValue(false)] public bool HasKatakana { get; set; } /// /// JIS第1水準漢字を追加するか /// [DisplayName("追加文字 第1水準漢字")] [Description("フォント生成時にJIS第1水準漢字2,965文字を追加します。")] [DefaultValue(false)] public bool HasJisKanjiLevel1 { get; set; } /// /// JIS第2水準漢字を追加するか /// [DisplayName("追加文字 第2水準漢字")] [Description("フォント生成時にJIS第2水準漢字3,390文字を追加します。")] [DefaultValue(false)] public bool HasJisKanjiLevel2 { get; set; } /// /// ギリシャ文字 /// [DisplayName("追加文字 ギリシャ文字")] [Description("フォント生成時にギリシャ文字を追加します。")] [DefaultValue(false)] public bool HasGreekLetters { get; set; } /// /// ギリシャ文字 /// [DisplayName("追加文字 キリル文字")] [Description("フォント生成時にキリル文字を追加します。")] [DefaultValue(false)] public bool HasCyrillicLetters { get; set; } /// /// ギリシャ文字 /// [DisplayName("追加文字 罫線")] [Description("フォント生成時に\"└┏┫\"等の罫線文字を追加します。")] [DefaultValue(false)] public bool HasBoxCharacters { get; set; } /// /// 追加文字の指定 /// [DisplayName("追加文字 テキスト")] [Description("フォント生成時に追加する文字列を指定できます(重複化)")] public string Text { get; set; } /// /// 読み込むメッセージファイル名 /// [DisplayName("追加文字 テキストファイル名")] [Description("メッセージテキストが含まれているテキストファイル名を指定します。" +"セミコロン(;)で区切って複数ファイル指定可能です。")] public string MessageFilenames { get; set; } /// /// アウトラインの太さ /// [DisplayName("文字装飾 アウトライン")] [Description("文字生成時のアウトラインの太さ(ピクセル単位)を指定します。" +"小数点以下の数字を指定することができます。")] [DefaultValue(0)] public float OutlineThickness { get; set; } /// /// アウトラインの色 /// [DisplayName("文字装飾 アウトライン色")] [Description("文字のアウトライン色を指定します。")] [DefaultValue(typeof(Color), "64, 64, 64, 255")] public Color OutlineColor { get; set; } /// /// アウトライン形状 /// [DisplayName("文字装飾 アウトライン形状")] [Description("文字生成時のアウトラインの形状を指定します。" + "Miter(鋭角)、Bevel(ベベル)、Round(丸形)から指定します。")] [DefaultValue(PenLineJoin.Miter)] public PenLineJoin OutlineShape { get; set; } /// /// アウトライン描画方法 /// [DisplayName("文字装飾 アウトライン描画方法")] [Description("文字生成時のアウトライン描画方法を指定します。" + "StrokeOverFill: アウトラインを文字に重ねる、" + "FillOverStroke: 文字をアウトラインに重ねる、" + "StrokeOnly: アウトラインのみ描画から指定します。")] [DefaultValue(OutlineStroke.StrokeOverFill)] public OutlineStroke OutlineStroke { get; set; } /// /// 文字色 /// [DisplayName("文字装飾 文字色")] [Description("文字色を指定します。グラデーション使用時には無視されます。")] [DefaultValue(typeof(Color), "255, 255, 255, 255")] public Color FontColor { get; set; } /// /// グラデーション開始色 /// [DisplayName("文字装飾 グラデーション使用")] [Description("文字描画時にグラデーションを使用するかを指定します。")] [DefaultValue(false)] public bool UseGradient { get; set; } /// /// グラデーション開始色 /// [DisplayName("文字装飾 グラデーション開始色")] [Description("グラデーション開始色")] [DefaultValue(typeof(Color), "64, 128, 255, 255")] public Color GradientBeginColor { get; set; } /// /// グラデーション終端色 /// [DisplayName("文字装飾 グラデーション終端色")] [Description("グラデーション終端色")] [DefaultValue(typeof(Color), "0, 0, 128, 255")] public Color GradientEndColor { get; set; } /// /// グラデーション角度 /// [DisplayName("文字装飾 グラデーション角度")] [Description("グラデーション角度")] [DefaultValue(90)] public int GradientAngle { get; set; } /// /// 文字テクスチャフォーマット /// [DisplayName("文字テクスチャフォーマット")] [Description("使用する文字テクスチャフォーマットを指定します。Autoを設定すると、" +"単色フォントではDXT3、アウトライン文字ではBgra4444、グラデーション文字では" +"Colorを自動的に使用します。")] [DefaultValue(WpfTextureFormat.Auto)] public WpfTextureFormat TextureFormat { get; set; } #endregion #region 初期化 /// /// コンストラクタ /// public WpfFontDescriptionProcessor() { // 既定値の設定 HasAsciiCharacters = true; OutlineColor = new Color(64, 64, 64, 255); OutlineShape = PenLineJoin.Miter; OutlineStroke = OutlineStroke.StrokeOverFill; FontColor = new Color(255, 255, 255, 255); GradientBeginColor = new Color(64, 128, 255, 255); GradientEndColor = new Color(0, 0, 128, 255); GradientAngle = 90; } #endregion #region コンテント・パイプライン処理 /// /// FontDescriptionを処理する /// public override WpfSpriteFontContent Process(LocalizedFontDescription input, ContentProcessorContext context) { this.input = input; // 出力先のFontContentの生成 fontContent = new WpfSpriteFontContent(); GetLocalisedResX(input, context); // 文字の追加 AddExtraCharacters(context); // WPFフォントの生成 CreateWpfFont(context); // 文字グリフの処理 ProcessGlyphs(context); context.Logger.LogImportantMessage("処理文字数 {0}", input.Characters.Count); // その他の情報の設定 fontContent.LineSpacing = (int)(glyphTypeface.Height * fontSize); fontContent.Spacing = input.Spacing; fontContent.DefaultCharacter = input.DefaultCharacter; return fontContent; } private static void GetLocalisedResX(LocalizedFontDescription input, ContentProcessorContext context) { // Scan each .resx file in turn. foreach (string resourceFile in input.ResourceFiles) { string absolutePath = Path.GetFullPath(resourceFile); // Make sure the .resx file really does exist. if (!File.Exists(absolutePath)) { throw new InvalidContentException("Can't find " + absolutePath); } // Load the .resx data. XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(absolutePath); // Scan each string from the .resx file. foreach (XmlNode xmlNode in xmlDocument.SelectNodes("root/data/value")) { string resourceString = xmlNode.InnerText; // Scan each character of the string. foreach (char usedCharacter in resourceString) { input.Characters.Add(usedCharacter); } } // Mark that this font should be rebuilt if the resource file changes. context.AddDependency(absolutePath); } } #endregion #region 文字追加処理 /// /// プロセッサーパラメーターで指定された文字をinput.Chractersへ追加する /// void AddExtraCharacters(ContentProcessorContext context) { // ASCII文字の追加 if (HasAsciiCharacters) { for (char c = '\u0020'; c <= '\u007e'; ++c) input.Characters.Add(c); } // JISコード文字の追加 if (HasZenkakuLatinLetters) AddCharacters(JisCode.GetLatinLetters()); if (HasSpecialCharacters) AddCharacters(JisCode.GetSpecialCharacters()); if (HasHiragana) AddCharacters(JisCode.GetHiragana()); if (HasKatakana) AddCharacters(JisCode.GetKatakana()); if (HasJisKanjiLevel1) AddCharacters(JisCode.GetKanjiLevel1()); if (HasJisKanjiLevel2) AddCharacters(JisCode.GetKanjiLevel2()); if (HasCyrillicLetters) AddCharacters(JisCode.GetCyrillicLetters()); if (HasGreekLetters) AddCharacters(JisCode.GetGreekLetters()); if (HasBoxCharacters) AddCharacters(JisCode.GetBoxDrawingCharacters()); // 指定されたテキストファイル内の文字列を追加 if (!String.IsNullOrEmpty(Text)) AddCharacters(Text); if (!String.IsNullOrEmpty(MessageFilenames)) { foreach (var token in MessageFilenames.Split(new char[] { ';' })) { // 文字列両端の余分な余白と'"'を取り除く var filename = token.Trim(); filename = filename.Trim(new char[] { '"' }); filename = filename.Trim(); // '"'内の余分な余白も取り除く AddCharacters(filename, context); } } // DefaultCharacterも忘れずに if (input.DefaultCharacter.HasValue) input.Characters.Add(input.DefaultCharacter.Value); } /// /// 指定されたテキストファイル内の文字を追加する /// /// テキストファイル名 void AddCharacters(string filename, ContentProcessorContext context) { // 指定されたファイルから文字列を読み込む // FontDescription.Charctarsに追加する try { if (!File.Exists(filename)) { throw new FileNotFoundException(String.Format( "MessageFilenamesで指定されたファイル[{0}]が存在しません", Path.GetFullPath(filename))); } foreach (var line in File.ReadLines(filename, Encoding.Default)) { AddCharacters(line); } // CPにファイル依存していることを教える context.AddDependency(Path.GetFullPath(filename)); } catch (Exception e) { // 予期しない例外が発生 context.Logger.LogImportantMessage("例外発生!! {0}", e.Message); throw e; } } /// /// 指定された文字コレクションを追加する /// void AddCharacters(IEnumerable addingCharacters) { foreach (var c in addingCharacters) { input.Characters.Add(c); } } #endregion #region WPFフォント生成 /// /// FontDescriptionからWPFフォントを生成する /// void CreateWpfFont(ContentProcessorContext context) { // FontDescriptionでのフォントサイズは72DPIで指定されているので // WPFのDIU(Device Independent Unit)に変換する fontSize = (float)(input.Size * (WpfDiu / 72.0)); // フォントスタイルの変換 var fontWeight = ((input.Style & FontDescriptionStyle.Bold) == FontDescriptionStyle.Bold) ? FontWeights.Bold : FontWeights.Regular; var fontStyle = ((input.Style & FontDescriptionStyle.Italic) == FontDescriptionStyle.Italic) ? FontStyles.Italic : FontStyles.Normal; // Typefaceの生成 typeface = new Typeface(new FontFamily(input.FontName), fontStyle, fontWeight, FontStretches.Normal); if (typeface == null) { throw new InvalidOperationException( "フォント\"{0}\"の生成に失敗しました。" + "指定されたフォントがインストールされているか確認してください。"); } // GlyphTypefaceの取得 if (typeface.TryGetGlyphTypeface(out glyphTypeface) == false) { throw new InvalidOperationException( "フォント\"{0}\"のGlyphTypeface生成に失敗しました。"); } } #endregion #region グリフ処理 void ProcessGlyphs(ContentProcessorContext context) { // 文字描画に必要な情報設定 if (UseGradient) { textBrush = new LinearGradientBrush( ToWpfColor(this.GradientBeginColor), ToWpfColor(this.GradientEndColor), GradientAngle); } else { textBrush = new SolidColorBrush(ToWpfColor(FontColor)); } if (OutlineThickness > 0) { outlinePen = new Pen(new SolidColorBrush(ToWpfColor(OutlineColor)), OutlineThickness); outlinePen.LineJoin = OutlineShape; } else { outlinePen = null; } renderTarget = null; drawingVisual = new DrawingVisual(); // 登録文字をUnicode順に並び替える、これはXNAが実行時に文字グリフを // バイナリ検索しているので重要なステップ var characters = from c in input.Characters orderby c select c; var layouter = new BoxLayouter(); // 一文字ずつつ描画し、グリフ情報を生成する foreach (char c in characters) { // 文字描画 var glyphBounds = RenderCharacter(c); // ピクセル情報の取得 int stride = renderTarget.PixelWidth; uint[] pixels = new uint[stride * renderTarget.PixelHeight]; renderTarget.CopyPixels(pixels, stride * sizeof(uint), 0); // Black-Boxを取得し、必要な領域の画像イメージを取得 glyphBounds = NarrowerGlyph(pixels, stride, glyphBounds); var blackBox = GetBlackBox(pixels, stride, glyphBounds); pixels = new uint[blackBox.Width * blackBox.Height]; renderTarget.CopyPixels( ToInt32Rect(blackBox), pixels, blackBox.Width * sizeof(uint), 0); // カーニング情報の取得 var kerning = GetKerning(c, blackBox); // FontContentへの設定 fontContent.CharacterMap.Add(c); fontContent.Kerning.Add(kerning); fontContent.Glyphs.Add(new Rectangle( 0, 0, blackBox.Width, blackBox.Height)); fontContent.Cropping.Add(new Rectangle( blackBox.X - glyphBounds.X, blackBox.Y - glyphBounds.Y, glyphBounds.Width, glyphBounds.Height)); // レイアウト用アイテムとして追加 layouter.Add(new BoxLayoutItem{Bounds = blackBox, Tag = pixels}); } // テクスチャ処理 ProcessTexture(layouter); } /// /// 文字グリフからテクスチャを生成する /// /// void ProcessTexture(BoxLayouter layouter) { // レイアウト、複数の矩形を1つの矩形内に並べる int width, height; layouter.Layout(out width, out height); // 配置後のグリフを画像へ書き込む var bitmap = new PixelBitmapContent(width, height); for (int i = 0; i < layouter.Items.Count; ++i) { // グリフ位置情報の追加 var rc = fontContent.Glyphs[i]; rc.X = layouter.Items[i].Bounds.X; rc.Y = layouter.Items[i].Bounds.Y; fontContent.Glyphs[i] = rc; // 個々のグリフ画像をひとつの画像へ追加する var pixels = layouter.Items[i].Tag as uint[]; int idx = 0; for (int y = 0; y < rc.Height; ++y) { for(int x = 0; x < rc.Width; ++x) { int r = (int)((pixels[idx] & 0x00ff0000) >> 16); int g = (int)((pixels[idx] & 0x0000ff00) >> 8); int b = (int)((pixels[idx] & 0x000000ff) >> 0); int a = (int)((pixels[idx] & 0xff000000) >> 24); bitmap.SetPixel(rc.X + x, rc.Y + y, new Color(r, g, b, a)); ++idx; } } } // 文字画像をまとめた画像をテクスチャへ変換する fontContent.Texture = new Texture2DContent(); switch(TextureFormat) { case WpfTextureFormat.Auto: if (UseGradient) { // グラデーション使用していればColorフォーマット fontContent.Texture.Mipmaps = bitmap; } else if (OutlineThickness > 0) { // アウトラインのみ使用していればBgra4444フォーマット fontContent.Texture.Mipmaps = bitmap; fontContent.Texture.ConvertBitmapType( typeof(PixelBitmapContent)); } else { // それ以外の単色フォントであれば単色に特化したDXT3圧縮をする fontContent.Texture.Mipmaps = SingleColorDxtCompressor.Compress(bitmap, FontColor); } break; case WpfTextureFormat.Bgra4444: fontContent.Texture.Mipmaps = bitmap; fontContent.Texture.ConvertBitmapType( typeof(PixelBitmapContent)); break; case WpfTextureFormat.Color: fontContent.Texture.Mipmaps = bitmap; break; } } /// /// カーニング情報の取得 /// protected Vector3 GetKerning(char character, Rectangle blackBox) { // Left/RightBearing情報が取得できれば、その情報を、 // できなければBlack-box値をカーニング情報として使用する Vector3 kerning = Vector3.Zero; ushort glyphIdx; if (glyphTypeface.CharacterToGlyphMap.TryGetValue(character, out glyphIdx)) { var leftSideBearing = glyphTypeface.LeftSideBearings[glyphIdx]; var rightSideBearing = glyphTypeface.RightSideBearings[glyphIdx]; kerning.X = SnapPixel(leftSideBearing * fontSize); kerning.Z = SnapPixel(rightSideBearing * fontSize); } kerning.Y = blackBox.Width; return kerning; } /// /// ピクセル単位のカーニング値取得 /// static float SnapPixel(double value) { // WPFの描画結果に合わせる為のバイアス(トライ&エラーの産物) var bias = 0.0937456; if (value > 0) return (float)Math.Floor(value + bias); return (float)Math.Ceiling(value - bias); } #endregion #region 文字描画 /// /// 文字描画に必要なサイズのレンダーターゲットを用意する /// /// 横幅 /// 高さ void EnsureRenderTargetSize(int width, int height) { if (renderTarget == null || renderTarget.Width < width || renderTarget.Height < height) { // 32ピクセル単位で生成する renderTarget = new RenderTargetBitmap( width + (width % 32), height + (height % 32), WpfDiu, WpfDiu, PixelFormats.Pbgra32); } } /// /// 一文字描画 /// /// /// /// 独自の文字描画を使用する時はこのメソッドをオーバーライドする /// protected virtual Rectangle RenderCharacter(char character) { // フォントサイズの取得 var formattedText = new FormattedText( new String(character, 1), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, fontSize, textBrush); // 描画領域の計算。余裕を持って1.5倍のサイズにする var width = Math.Max((int)Math.Ceiling( formattedText.Width * 1.5 + OutlineThickness), 1); var height = Math.Max((int)Math.Ceiling( formattedText.Height * 1.5 + OutlineThickness), 1); // レンダーターゲットサイズの確保 EnsureRenderTargetSize(width, height); // 暫定的なグリフ位置の取得 int fontWidth = Math.Max((int)Math.Ceiling(formattedText.Width), 1); int fontHeight = Math.Max((int)Math.Ceiling(formattedText.Height), 1); Rectangle rc = new Rectangle( (renderTarget.PixelWidth - fontWidth) / 2, (renderTarget.PixelHeight - fontHeight) / 2, fontWidth, fontHeight); // レンダーターゲットへの文字描画 using (DrawingContext dc = drawingVisual.RenderOpen()) { var pos = new System.Windows.Point(rc.X, rc.Y); if (outlinePen != null) { var geometry = formattedText.BuildGeometry(pos); switch(OutlineStroke) { case OutlineStroke.StrokeOverFill: dc.DrawGeometry(textBrush, outlinePen, geometry); break; case OutlineStroke.FillOverStroke: dc.DrawGeometry(null, outlinePen, geometry); dc.DrawGeometry(textBrush, null, geometry); break; case OutlineStroke.StrokeOnly: dc.DrawGeometry(null, outlinePen, geometry); break; } } else { dc.DrawText(formattedText, pos); } } renderTarget.Clear(); renderTarget.Render(drawingVisual); return rc; } #endregion #region グリフ処理の為のヘルパーメソッド /// /// 実際の文字グリフ幅を画像から取得する /// /// /// WPF文字描画時では実際の文字グリフ(Black-Box)の周りにBearing値の分だけ /// 左右に空き領域ができる場合があるので、ここでは画像データから /// Black-Box領域部分を取得している。 /// また、アウトライン描画等のGeometryを使った文字描画をすると /// 文字のBlack-Box領域より大きくなる場合があるので、その場合にも対処している /// Rectangle NarrowerGlyph(uint[] pixels, int stride, Rectangle bounds) { int left = bounds.X; int right = bounds.Right - 1; int width = renderTarget.PixelWidth; int height = renderTarget.PixelHeight; // ピクセルデータがある左端を走査 while (left > 0 && !IsEmptyColumn(pixels, stride, left, 0, height)) left--; while ((left < right) && IsEmptyColumn(pixels, stride, left, 0, height)) left++; // ピクセルデータがある右端を走査 while ((right < width) && !IsEmptyColumn(pixels, stride, right, 0, height)) right++; right = Math.Min(right, width - 1); while ((left <= right) && IsEmptyColumn(pixels, stride, right, 0, height)) right--; // スペースキャラクターだった(全てが透明色の文字) if (right < left) { left = right = 0; } // グリフサイズ調整 bounds.X = left; bounds.Width = right - left + 1; return bounds; } /// /// Black-Box領域を取得する /// /// /// NarrowerGlyphを読んだ後に呼ぶこと /// NarroerGlyphメソッドでBlack-Boxの左右値は既に求められているので /// ここではBlack-Boxの上下端を割り出している /// Rectangle GetBlackBox(uint[] pixels, int stride, Rectangle bounds) { int x1 = bounds.X; int x2 = bounds.Right; int top = bounds.Y; int bottom = bounds.Bottom - 1; int height = renderTarget.PixelHeight; // ピクセルデータがある上端を走査 while ((0 < top) && !IsEmptyLine(pixels, stride, top, x1, x2)) top--; while ((top < bottom) && IsEmptyLine(pixels, stride, top, x1, x2)) top++; // ピクセルデータがある下端を走査 while ((bottom < height) && !IsEmptyLine(pixels, stride, bottom, x1, x2)) bottom++; bottom = Math.Min(bottom, height- 1); while ((top <= bottom) && IsEmptyLine(pixels, stride, bottom, x1, x2)) bottom--; // スペースキャラクターだった(全てが透明色の文字) if (bottom < top) { top = bottom = 0; } // グリフサイズ調整 bounds.Y = top; bounds.Height = bottom - top + 1; return bounds; } /// /// 画像の指定された列に透明色以外のピクセルが存在するか? /// static bool IsEmptyColumn(uint[] pixels, int stride, int x, int y1, int y2) { var idx = y1 * stride + x; for (int y = y1; y < y2; ++y, idx += stride) { if (pixels[idx] != TransparentPixel) return false; } return true; } /// /// 画像の指定された行に透明色以外のピクセルが存在するか? /// static bool IsEmptyLine(uint[] pixels, int stride, int y, int x1, int x2) { var idx = y * stride + x1; for (int x = x1; x < x2; ++x, ++idx) { if (pixels[idx] != TransparentPixel) return false; } return true; } #endregion #region その他のヘルパーメソッド /// /// XNAのColor構造体からWPFのColorへ変換する /// static System.Windows.Media.Color ToWpfColor(Color color) { return System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B); } /// /// XNAのRectangle構造体からInt32Rectへ変換する /// static Int32Rect ToInt32Rect(Rectangle rc) { return new Int32Rect(rc.X, rc.Y, rc.Width, rc.Height); } #endregion #region 定数 // WPFのDIU(Device Independent Unit) const double WpfDiu = 96; // 透明色 const uint TransparentPixel = 0; #endregion #region プライベートフィールド // 処理中のFontDescription FontDescription input; // 出力先 WpfSpriteFontContent fontContent; // フォントサイズ(DIU) float fontSize; // WPFフォント情報 Typeface typeface; GlyphTypeface glyphTypeface; // テキスト描画用ブラシ Brush textBrush; // アウトライン描画用ペン Pen outlinePen; // 一文字を描画するためのレンダーターゲット RenderTargetBitmap renderTarget; // 文字描画用のDrawingVisual DrawingVisual drawingVisual; #endregion } }