WpfFontDescriptionProcessor.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. #region Using ステートメント
  2. using Microsoft.Xna.Framework;
  3. using Microsoft.Xna.Framework.Content.Pipeline;
  4. using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
  5. using Microsoft.Xna.Framework.Graphics.PackedVector;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.ComponentModel;
  9. using System.Globalization;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Text;
  13. using System.Windows;
  14. using System.Windows.Media;
  15. using System.Windows.Media.Imaging;
  16. using System.Xml;
  17. using Color = Microsoft.Xna.Framework.Color;
  18. #endregion
  19. namespace WpfFontPipeline
  20. {
  21. /// <summary>
  22. /// WPFフォントプロセッサーで使用するテクスチャフォーマット
  23. /// </summary>
  24. public enum WpfTextureFormat
  25. {
  26. Auto, // 自動:単色の場合にはDXT3、アウトライン使用でBgra444、
  27. // グラデーション使用でColorとフォーマットを切り替える
  28. Color,
  29. Bgra4444
  30. }
  31. /// <summary>
  32. /// アウトライン描画方法
  33. /// </summary>
  34. public enum OutlineStroke
  35. {
  36. StrokeOverFill, // 文字本体描画の後にアウトラインを描画する
  37. FillOverStroke, // アウトラインを描画した後に文字本体描画する
  38. StrokeOnly // アウトラインのみを描画する
  39. }
  40. /// <summary>
  41. /// WPF文字描画を使ったフォントプロセッサー
  42. /// </summary>
  43. [ContentProcessor(DisplayName = "WPF フォントプロセッサー")]
  44. public class WpfFontDescriptionProcessor :
  45. ContentProcessor<LocalizedFontDescription, WpfSpriteFontContent>
  46. {
  47. #region プロパティ
  48. /// <summary>
  49. /// 半角英数時を生成文字として追加するか
  50. /// </summary>
  51. [DisplayName("追加文字 ASCII")]
  52. [Description("フォント生成時に半角英数字、記号を追加します。")]
  53. [DefaultValue(true)]
  54. public bool HasAsciiCharacters { get; set; }
  55. /// <summary>
  56. /// 全角英数時を生成文字として追加するか
  57. /// </summary>
  58. [DisplayName("追加文字 全角英数字")]
  59. [Description("フォント生成時に全角英数字を追加します。")]
  60. [DefaultValue(false)]
  61. public bool HasZenkakuLatinLetters { get; set; }
  62. /// <summary>
  63. /// 特殊記号文字を追加するか
  64. /// </summary>
  65. [DisplayName("追加文字 記号")]
  66. [Description("フォント生成時に\"「」…、\"等の記号を追加します。")]
  67. [DefaultValue(false)]
  68. public bool HasSpecialCharacters { get; set; }
  69. /// <summary>
  70. /// ひらがな文字を追加するか
  71. /// </summary>
  72. [DisplayName("追加文字 ひらがな")]
  73. [Description("フォント生成時にひらがな文字を追加します。")]
  74. [DefaultValue(false)]
  75. public bool HasHiragana { get; set; }
  76. /// <summary>
  77. /// カタカナ文字を追加するか
  78. /// </summary>
  79. [DisplayName("追加文字 カタカナ")]
  80. [Description("フォント生成時にカタカナ文字を追加します。")]
  81. [DefaultValue(false)]
  82. public bool HasKatakana { get; set; }
  83. /// <summary>
  84. /// JIS第1水準漢字を追加するか
  85. /// </summary>
  86. [DisplayName("追加文字 第1水準漢字")]
  87. [Description("フォント生成時にJIS第1水準漢字2,965文字を追加します。")]
  88. [DefaultValue(false)]
  89. public bool HasJisKanjiLevel1 { get; set; }
  90. /// <summary>
  91. /// JIS第2水準漢字を追加するか
  92. /// </summary>
  93. [DisplayName("追加文字 第2水準漢字")]
  94. [Description("フォント生成時にJIS第2水準漢字3,390文字を追加します。")]
  95. [DefaultValue(false)]
  96. public bool HasJisKanjiLevel2 { get; set; }
  97. /// <summary>
  98. /// ギリシャ文字
  99. /// </summary>
  100. [DisplayName("追加文字 ギリシャ文字")]
  101. [Description("フォント生成時にギリシャ文字を追加します。")]
  102. [DefaultValue(false)]
  103. public bool HasGreekLetters { get; set; }
  104. /// <summary>
  105. /// ギリシャ文字
  106. /// </summary>
  107. [DisplayName("追加文字 キリル文字")]
  108. [Description("フォント生成時にキリル文字を追加します。")]
  109. [DefaultValue(false)]
  110. public bool HasCyrillicLetters { get; set; }
  111. /// <summary>
  112. /// ギリシャ文字
  113. /// </summary>
  114. [DisplayName("追加文字 罫線")]
  115. [Description("フォント生成時に\"└┏┫\"等の罫線文字を追加します。")]
  116. [DefaultValue(false)]
  117. public bool HasBoxCharacters { get; set; }
  118. /// <summary>
  119. /// 追加文字の指定
  120. /// </summary>
  121. [DisplayName("追加文字 テキスト")]
  122. [Description("フォント生成時に追加する文字列を指定できます(重複化)")]
  123. public string Text { get; set; }
  124. /// <summary>
  125. /// 読み込むメッセージファイル名
  126. /// </summary>
  127. [DisplayName("追加文字 テキストファイル名")]
  128. [Description("メッセージテキストが含まれているテキストファイル名を指定します。"
  129. +"セミコロン(;)で区切って複数ファイル指定可能です。")]
  130. public string MessageFilenames { get; set; }
  131. /// <summary>
  132. /// アウトラインの太さ
  133. /// </summary>
  134. [DisplayName("文字装飾 アウトライン")]
  135. [Description("文字生成時のアウトラインの太さ(ピクセル単位)を指定します。"
  136. +"小数点以下の数字を指定することができます。")]
  137. [DefaultValue(0)]
  138. public float OutlineThickness { get; set; }
  139. /// <summary>
  140. /// アウトラインの色
  141. /// </summary>
  142. [DisplayName("文字装飾 アウトライン色")]
  143. [Description("文字のアウトライン色を指定します。")]
  144. [DefaultValue(typeof(Color), "64, 64, 64, 255")]
  145. public Color OutlineColor { get; set; }
  146. /// <summary>
  147. /// アウトライン形状
  148. /// </summary>
  149. [DisplayName("文字装飾 アウトライン形状")]
  150. [Description("文字生成時のアウトラインの形状を指定します。"
  151. + "Miter(鋭角)、Bevel(ベベル)、Round(丸形)から指定します。")]
  152. [DefaultValue(PenLineJoin.Miter)]
  153. public PenLineJoin OutlineShape { get; set; }
  154. /// <summary>
  155. /// アウトライン描画方法
  156. /// </summary>
  157. [DisplayName("文字装飾 アウトライン描画方法")]
  158. [Description("文字生成時のアウトライン描画方法を指定します。"
  159. + "StrokeOverFill: アウトラインを文字に重ねる、"
  160. + "FillOverStroke: 文字をアウトラインに重ねる、"
  161. + "StrokeOnly: アウトラインのみ描画から指定します。")]
  162. [DefaultValue(OutlineStroke.StrokeOverFill)]
  163. public OutlineStroke OutlineStroke { get; set; }
  164. /// <summary>
  165. /// 文字色
  166. /// </summary>
  167. [DisplayName("文字装飾 文字色")]
  168. [Description("文字色を指定します。グラデーション使用時には無視されます。")]
  169. [DefaultValue(typeof(Color), "255, 255, 255, 255")]
  170. public Color FontColor { get; set; }
  171. /// <summary>
  172. /// グラデーション開始色
  173. /// </summary>
  174. [DisplayName("文字装飾 グラデーション使用")]
  175. [Description("文字描画時にグラデーションを使用するかを指定します。")]
  176. [DefaultValue(false)]
  177. public bool UseGradient { get; set; }
  178. /// <summary>
  179. /// グラデーション開始色
  180. /// </summary>
  181. [DisplayName("文字装飾 グラデーション開始色")]
  182. [Description("グラデーション開始色")]
  183. [DefaultValue(typeof(Color), "64, 128, 255, 255")]
  184. public Color GradientBeginColor { get; set; }
  185. /// <summary>
  186. /// グラデーション終端色
  187. /// </summary>
  188. [DisplayName("文字装飾 グラデーション終端色")]
  189. [Description("グラデーション終端色")]
  190. [DefaultValue(typeof(Color), "0, 0, 128, 255")]
  191. public Color GradientEndColor { get; set; }
  192. /// <summary>
  193. /// グラデーション角度
  194. /// </summary>
  195. [DisplayName("文字装飾 グラデーション角度")]
  196. [Description("グラデーション角度")]
  197. [DefaultValue(90)]
  198. public int GradientAngle { get; set; }
  199. /// <summary>
  200. /// 文字テクスチャフォーマット
  201. /// </summary>
  202. [DisplayName("文字テクスチャフォーマット")]
  203. [Description("使用する文字テクスチャフォーマットを指定します。Autoを設定すると、"
  204. +"単色フォントではDXT3、アウトライン文字ではBgra4444、グラデーション文字では"
  205. +"Colorを自動的に使用します。")]
  206. [DefaultValue(WpfTextureFormat.Auto)]
  207. public WpfTextureFormat TextureFormat { get; set; }
  208. #endregion
  209. #region 初期化
  210. /// <summary>
  211. /// コンストラクタ
  212. /// </summary>
  213. public WpfFontDescriptionProcessor()
  214. {
  215. // 既定値の設定
  216. HasAsciiCharacters = true;
  217. OutlineColor = new Color(64, 64, 64, 255);
  218. OutlineShape = PenLineJoin.Miter;
  219. OutlineStroke = OutlineStroke.StrokeOverFill;
  220. FontColor = new Color(255, 255, 255, 255);
  221. GradientBeginColor = new Color(64, 128, 255, 255);
  222. GradientEndColor = new Color(0, 0, 128, 255);
  223. GradientAngle = 90;
  224. }
  225. #endregion
  226. #region コンテント・パイプライン処理
  227. /// <summary>
  228. /// FontDescriptionを処理する
  229. /// </summary>
  230. public override WpfSpriteFontContent Process(LocalizedFontDescription input,
  231. ContentProcessorContext context)
  232. {
  233. this.input = input;
  234. // 出力先のFontContentの生成
  235. fontContent = new WpfSpriteFontContent();
  236. GetLocalisedResX(input, context);
  237. // 文字の追加
  238. AddExtraCharacters(context);
  239. // WPFフォントの生成
  240. CreateWpfFont(context);
  241. // 文字グリフの処理
  242. ProcessGlyphs(context);
  243. context.Logger.LogImportantMessage("処理文字数 {0}", input.Characters.Count);
  244. // その他の情報の設定
  245. fontContent.LineSpacing = (int)(glyphTypeface.Height * fontSize);
  246. fontContent.Spacing = input.Spacing;
  247. fontContent.DefaultCharacter = input.DefaultCharacter;
  248. return fontContent;
  249. }
  250. private static void GetLocalisedResX(LocalizedFontDescription input, ContentProcessorContext context)
  251. {
  252. // Scan each .resx file in turn.
  253. foreach (string resourceFile in input.ResourceFiles)
  254. {
  255. string absolutePath = Path.GetFullPath(resourceFile);
  256. // Make sure the .resx file really does exist.
  257. if (!File.Exists(absolutePath))
  258. {
  259. throw new InvalidContentException("Can't find " + absolutePath);
  260. }
  261. // Load the .resx data.
  262. XmlDocument xmlDocument = new XmlDocument();
  263. xmlDocument.Load(absolutePath);
  264. // Scan each string from the .resx file.
  265. foreach (XmlNode xmlNode in xmlDocument.SelectNodes("root/data/value"))
  266. {
  267. string resourceString = xmlNode.InnerText;
  268. // Scan each character of the string.
  269. foreach (char usedCharacter in resourceString)
  270. {
  271. input.Characters.Add(usedCharacter);
  272. }
  273. }
  274. // Mark that this font should be rebuilt if the resource file changes.
  275. context.AddDependency(absolutePath);
  276. }
  277. }
  278. #endregion
  279. #region 文字追加処理
  280. /// <summary>
  281. /// プロセッサーパラメーターで指定された文字をinput.Chractersへ追加する
  282. /// </summary>
  283. void AddExtraCharacters(ContentProcessorContext context)
  284. {
  285. // ASCII文字の追加
  286. if (HasAsciiCharacters)
  287. {
  288. for (char c = '\u0020'; c <= '\u007e'; ++c)
  289. input.Characters.Add(c);
  290. }
  291. // JISコード文字の追加
  292. if (HasZenkakuLatinLetters) AddCharacters(JisCode.GetLatinLetters());
  293. if (HasSpecialCharacters) AddCharacters(JisCode.GetSpecialCharacters());
  294. if (HasHiragana) AddCharacters(JisCode.GetHiragana());
  295. if (HasKatakana) AddCharacters(JisCode.GetKatakana());
  296. if (HasJisKanjiLevel1) AddCharacters(JisCode.GetKanjiLevel1());
  297. if (HasJisKanjiLevel2) AddCharacters(JisCode.GetKanjiLevel2());
  298. if (HasCyrillicLetters) AddCharacters(JisCode.GetCyrillicLetters());
  299. if (HasGreekLetters) AddCharacters(JisCode.GetGreekLetters());
  300. if (HasBoxCharacters) AddCharacters(JisCode.GetBoxDrawingCharacters());
  301. // 指定されたテキストファイル内の文字列を追加
  302. if (!String.IsNullOrEmpty(Text))
  303. AddCharacters(Text);
  304. if (!String.IsNullOrEmpty(MessageFilenames))
  305. {
  306. foreach (var token in MessageFilenames.Split(new char[] { ';' }))
  307. {
  308. // 文字列両端の余分な余白と'"'を取り除く
  309. var filename = token.Trim();
  310. filename = filename.Trim(new char[] { '"' });
  311. filename = filename.Trim(); // '"'内の余分な余白も取り除く
  312. AddCharacters(filename, context);
  313. }
  314. }
  315. // DefaultCharacterも忘れずに
  316. if (input.DefaultCharacter.HasValue)
  317. input.Characters.Add(input.DefaultCharacter.Value);
  318. }
  319. /// <summary>
  320. /// 指定されたテキストファイル内の文字を追加する
  321. /// </summary>
  322. /// <param name="filename">テキストファイル名</param>
  323. void AddCharacters(string filename, ContentProcessorContext context)
  324. {
  325. // 指定されたファイルから文字列を読み込む
  326. // FontDescription.Charctarsに追加する
  327. try
  328. {
  329. if (!File.Exists(filename))
  330. {
  331. throw new FileNotFoundException(String.Format(
  332. "MessageFilenamesで指定されたファイル[{0}]が存在しません",
  333. Path.GetFullPath(filename)));
  334. }
  335. foreach (var line in File.ReadLines(filename, Encoding.Default))
  336. {
  337. AddCharacters(line);
  338. }
  339. // CPにファイル依存していることを教える
  340. context.AddDependency(Path.GetFullPath(filename));
  341. }
  342. catch (Exception e)
  343. {
  344. // 予期しない例外が発生
  345. context.Logger.LogImportantMessage("例外発生!! {0}", e.Message);
  346. throw e;
  347. }
  348. }
  349. /// <summary>
  350. /// 指定された文字コレクションを追加する
  351. /// </summary>
  352. void AddCharacters(IEnumerable<char> addingCharacters)
  353. {
  354. foreach (var c in addingCharacters)
  355. {
  356. input.Characters.Add(c);
  357. }
  358. }
  359. #endregion
  360. #region WPFフォント生成
  361. /// <summary>
  362. /// FontDescriptionからWPFフォントを生成する
  363. /// </summary>
  364. void CreateWpfFont(ContentProcessorContext context)
  365. {
  366. // FontDescriptionでのフォントサイズは72DPIで指定されているので
  367. // WPFのDIU(Device Independent Unit)に変換する
  368. fontSize = (float)(input.Size * (WpfDiu / 72.0));
  369. // フォントスタイルの変換
  370. var fontWeight = ((input.Style & FontDescriptionStyle.Bold) ==
  371. FontDescriptionStyle.Bold) ? FontWeights.Bold : FontWeights.Regular;
  372. var fontStyle = ((input.Style & FontDescriptionStyle.Italic) ==
  373. FontDescriptionStyle.Italic) ? FontStyles.Italic : FontStyles.Normal;
  374. // Typefaceの生成
  375. typeface = new Typeface(new FontFamily(input.FontName),
  376. fontStyle, fontWeight, FontStretches.Normal);
  377. if (typeface == null)
  378. {
  379. throw new InvalidOperationException(
  380. "フォント\"{0}\"の生成に失敗しました。" +
  381. "指定されたフォントがインストールされているか確認してください。");
  382. }
  383. // GlyphTypefaceの取得
  384. if (typeface.TryGetGlyphTypeface(out glyphTypeface) == false)
  385. {
  386. throw new InvalidOperationException(
  387. "フォント\"{0}\"のGlyphTypeface生成に失敗しました。");
  388. }
  389. }
  390. #endregion
  391. #region グリフ処理
  392. void ProcessGlyphs(ContentProcessorContext context)
  393. {
  394. // 文字描画に必要な情報設定
  395. if (UseGradient)
  396. {
  397. textBrush = new LinearGradientBrush(
  398. ToWpfColor(this.GradientBeginColor),
  399. ToWpfColor(this.GradientEndColor),
  400. GradientAngle);
  401. }
  402. else
  403. {
  404. textBrush = new SolidColorBrush(ToWpfColor(FontColor));
  405. }
  406. if (OutlineThickness > 0)
  407. {
  408. outlinePen = new Pen(new SolidColorBrush(ToWpfColor(OutlineColor)),
  409. OutlineThickness);
  410. outlinePen.LineJoin = OutlineShape;
  411. }
  412. else
  413. {
  414. outlinePen = null;
  415. }
  416. renderTarget = null;
  417. drawingVisual = new DrawingVisual();
  418. // 登録文字をUnicode順に並び替える、これはXNAが実行時に文字グリフを
  419. // バイナリ検索しているので重要なステップ
  420. var characters = from c in input.Characters orderby c select c;
  421. var layouter = new BoxLayouter();
  422. // 一文字ずつつ描画し、グリフ情報を生成する
  423. foreach (char c in characters)
  424. {
  425. // 文字描画
  426. var glyphBounds = RenderCharacter(c);
  427. // ピクセル情報の取得
  428. int stride = renderTarget.PixelWidth;
  429. uint[] pixels = new uint[stride * renderTarget.PixelHeight];
  430. renderTarget.CopyPixels(pixels, stride * sizeof(uint), 0);
  431. // Black-Boxを取得し、必要な領域の画像イメージを取得
  432. glyphBounds = NarrowerGlyph(pixels, stride, glyphBounds);
  433. var blackBox = GetBlackBox(pixels, stride, glyphBounds);
  434. pixels = new uint[blackBox.Width * blackBox.Height];
  435. renderTarget.CopyPixels(
  436. ToInt32Rect(blackBox), pixels, blackBox.Width * sizeof(uint), 0);
  437. // カーニング情報の取得
  438. var kerning = GetKerning(c, blackBox);
  439. // FontContentへの設定
  440. fontContent.CharacterMap.Add(c);
  441. fontContent.Kerning.Add(kerning);
  442. fontContent.Glyphs.Add(new Rectangle(
  443. 0, 0, blackBox.Width, blackBox.Height));
  444. fontContent.Cropping.Add(new Rectangle(
  445. blackBox.X - glyphBounds.X,
  446. blackBox.Y - glyphBounds.Y,
  447. glyphBounds.Width, glyphBounds.Height));
  448. // レイアウト用アイテムとして追加
  449. layouter.Add(new BoxLayoutItem{Bounds = blackBox, Tag = pixels});
  450. }
  451. // テクスチャ処理
  452. ProcessTexture(layouter);
  453. }
  454. /// <summary>
  455. /// 文字グリフからテクスチャを生成する
  456. /// </summary>
  457. /// <param name="layouter"></param>
  458. void ProcessTexture(BoxLayouter layouter)
  459. {
  460. // レイアウト、複数の矩形を1つの矩形内に並べる
  461. int width, height;
  462. layouter.Layout(out width, out height);
  463. // 配置後のグリフを画像へ書き込む
  464. var bitmap = new PixelBitmapContent<Color>(width, height);
  465. for (int i = 0; i < layouter.Items.Count; ++i)
  466. {
  467. // グリフ位置情報の追加
  468. var rc = fontContent.Glyphs[i];
  469. rc.X = layouter.Items[i].Bounds.X;
  470. rc.Y = layouter.Items[i].Bounds.Y;
  471. fontContent.Glyphs[i] = rc;
  472. // 個々のグリフ画像をひとつの画像へ追加する
  473. var pixels = layouter.Items[i].Tag as uint[];
  474. int idx = 0;
  475. for (int y = 0; y < rc.Height; ++y)
  476. {
  477. for(int x = 0; x < rc.Width; ++x)
  478. {
  479. int r = (int)((pixels[idx] & 0x00ff0000) >> 16);
  480. int g = (int)((pixels[idx] & 0x0000ff00) >> 8);
  481. int b = (int)((pixels[idx] & 0x000000ff) >> 0);
  482. int a = (int)((pixels[idx] & 0xff000000) >> 24);
  483. bitmap.SetPixel(rc.X + x, rc.Y + y, new Color(r, g, b, a));
  484. ++idx;
  485. }
  486. }
  487. }
  488. // 文字画像をまとめた画像をテクスチャへ変換する
  489. fontContent.Texture = new Texture2DContent();
  490. switch(TextureFormat)
  491. {
  492. case WpfTextureFormat.Auto:
  493. if (UseGradient)
  494. {
  495. // グラデーション使用していればColorフォーマット
  496. fontContent.Texture.Mipmaps = bitmap;
  497. }
  498. else if (OutlineThickness > 0)
  499. {
  500. // アウトラインのみ使用していればBgra4444フォーマット
  501. fontContent.Texture.Mipmaps = bitmap;
  502. fontContent.Texture.ConvertBitmapType(
  503. typeof(PixelBitmapContent<Bgra4444>));
  504. }
  505. else
  506. {
  507. // それ以外の単色フォントであれば単色に特化したDXT3圧縮をする
  508. fontContent.Texture.Mipmaps =
  509. SingleColorDxtCompressor.Compress(bitmap, FontColor);
  510. }
  511. break;
  512. case WpfTextureFormat.Bgra4444:
  513. fontContent.Texture.Mipmaps = bitmap;
  514. fontContent.Texture.ConvertBitmapType(
  515. typeof(PixelBitmapContent<Bgra4444>));
  516. break;
  517. case WpfTextureFormat.Color:
  518. fontContent.Texture.Mipmaps = bitmap;
  519. break;
  520. }
  521. }
  522. /// <summary>
  523. /// カーニング情報の取得
  524. /// </summary>
  525. protected Vector3 GetKerning(char character, Rectangle blackBox)
  526. {
  527. // Left/RightBearing情報が取得できれば、その情報を、
  528. // できなければBlack-box値をカーニング情報として使用する
  529. Vector3 kerning = Vector3.Zero;
  530. ushort glyphIdx;
  531. if (glyphTypeface.CharacterToGlyphMap.TryGetValue(character, out glyphIdx))
  532. {
  533. var leftSideBearing = glyphTypeface.LeftSideBearings[glyphIdx];
  534. var rightSideBearing = glyphTypeface.RightSideBearings[glyphIdx];
  535. kerning.X = SnapPixel(leftSideBearing * fontSize);
  536. kerning.Z = SnapPixel(rightSideBearing * fontSize);
  537. }
  538. kerning.Y = blackBox.Width;
  539. return kerning;
  540. }
  541. /// <summary>
  542. /// ピクセル単位のカーニング値取得
  543. /// </summary>
  544. static float SnapPixel(double value)
  545. {
  546. // WPFの描画結果に合わせる為のバイアス(トライ&エラーの産物)
  547. var bias = 0.0937456;
  548. if (value > 0)
  549. return (float)Math.Floor(value + bias);
  550. return (float)Math.Ceiling(value - bias);
  551. }
  552. #endregion
  553. #region 文字描画
  554. /// <summary>
  555. /// 文字描画に必要なサイズのレンダーターゲットを用意する
  556. /// </summary>
  557. /// <param name="width">横幅</param>
  558. /// <param name="height">高さ</param>
  559. void EnsureRenderTargetSize(int width, int height)
  560. {
  561. if (renderTarget == null ||
  562. renderTarget.Width < width || renderTarget.Height < height)
  563. {
  564. // 32ピクセル単位で生成する
  565. renderTarget = new RenderTargetBitmap(
  566. width + (width % 32), height + (height % 32),
  567. WpfDiu, WpfDiu, PixelFormats.Pbgra32);
  568. }
  569. }
  570. /// <summary>
  571. /// 一文字描画
  572. /// </summary>
  573. /// <param name="character"></param>
  574. /// <remarks>
  575. /// 独自の文字描画を使用する時はこのメソッドをオーバーライドする
  576. /// </remarks>
  577. protected virtual Rectangle RenderCharacter(char character)
  578. {
  579. // フォントサイズの取得
  580. var formattedText = new FormattedText(
  581. new String(character, 1), CultureInfo.CurrentCulture,
  582. FlowDirection.LeftToRight, typeface, fontSize, textBrush);
  583. // 描画領域の計算。余裕を持って1.5倍のサイズにする
  584. var width = Math.Max((int)Math.Ceiling(
  585. formattedText.Width * 1.5 + OutlineThickness), 1);
  586. var height = Math.Max((int)Math.Ceiling(
  587. formattedText.Height * 1.5 + OutlineThickness), 1);
  588. // レンダーターゲットサイズの確保
  589. EnsureRenderTargetSize(width, height);
  590. // 暫定的なグリフ位置の取得
  591. int fontWidth = Math.Max((int)Math.Ceiling(formattedText.Width), 1);
  592. int fontHeight = Math.Max((int)Math.Ceiling(formattedText.Height), 1);
  593. Rectangle rc = new Rectangle(
  594. (renderTarget.PixelWidth - fontWidth) / 2,
  595. (renderTarget.PixelHeight - fontHeight) / 2,
  596. fontWidth, fontHeight);
  597. // レンダーターゲットへの文字描画
  598. using (DrawingContext dc = drawingVisual.RenderOpen())
  599. {
  600. var pos = new System.Windows.Point(rc.X, rc.Y);
  601. if (outlinePen != null)
  602. {
  603. var geometry = formattedText.BuildGeometry(pos);
  604. switch(OutlineStroke)
  605. {
  606. case OutlineStroke.StrokeOverFill:
  607. dc.DrawGeometry(textBrush, outlinePen, geometry);
  608. break;
  609. case OutlineStroke.FillOverStroke:
  610. dc.DrawGeometry(null, outlinePen, geometry);
  611. dc.DrawGeometry(textBrush, null, geometry);
  612. break;
  613. case OutlineStroke.StrokeOnly:
  614. dc.DrawGeometry(null, outlinePen, geometry);
  615. break;
  616. }
  617. }
  618. else
  619. {
  620. dc.DrawText(formattedText, pos);
  621. }
  622. }
  623. renderTarget.Clear();
  624. renderTarget.Render(drawingVisual);
  625. return rc;
  626. }
  627. #endregion
  628. #region グリフ処理の為のヘルパーメソッド
  629. /// <summary>
  630. /// 実際の文字グリフ幅を画像から取得する
  631. /// </summary>
  632. /// <remarks>
  633. /// WPF文字描画時では実際の文字グリフ(Black-Box)の周りにBearing値の分だけ
  634. /// 左右に空き領域ができる場合があるので、ここでは画像データから
  635. /// Black-Box領域部分を取得している。
  636. /// また、アウトライン描画等のGeometryを使った文字描画をすると
  637. /// 文字のBlack-Box領域より大きくなる場合があるので、その場合にも対処している
  638. /// </remarks>
  639. Rectangle NarrowerGlyph(uint[] pixels, int stride, Rectangle bounds)
  640. {
  641. int left = bounds.X;
  642. int right = bounds.Right - 1;
  643. int width = renderTarget.PixelWidth;
  644. int height = renderTarget.PixelHeight;
  645. // ピクセルデータがある左端を走査
  646. while (left > 0 && !IsEmptyColumn(pixels, stride, left, 0, height))
  647. left--;
  648. while ((left < right) && IsEmptyColumn(pixels, stride, left, 0, height))
  649. left++;
  650. // ピクセルデータがある右端を走査
  651. while ((right < width) && !IsEmptyColumn(pixels, stride, right, 0, height))
  652. right++;
  653. right = Math.Min(right, width - 1);
  654. while ((left <= right) && IsEmptyColumn(pixels, stride, right, 0, height))
  655. right--;
  656. // スペースキャラクターだった(全てが透明色の文字)
  657. if (right < left)
  658. {
  659. left = right = 0;
  660. }
  661. // グリフサイズ調整
  662. bounds.X = left;
  663. bounds.Width = right - left + 1;
  664. return bounds;
  665. }
  666. /// <summary>
  667. /// Black-Box領域を取得する
  668. /// </summary>
  669. /// <remarks>
  670. /// NarrowerGlyphを読んだ後に呼ぶこと
  671. /// NarroerGlyphメソッドでBlack-Boxの左右値は既に求められているので
  672. /// ここではBlack-Boxの上下端を割り出している
  673. /// </remarks>
  674. Rectangle GetBlackBox(uint[] pixels, int stride, Rectangle bounds)
  675. {
  676. int x1 = bounds.X;
  677. int x2 = bounds.Right;
  678. int top = bounds.Y;
  679. int bottom = bounds.Bottom - 1;
  680. int height = renderTarget.PixelHeight;
  681. // ピクセルデータがある上端を走査
  682. while ((0 < top) && !IsEmptyLine(pixels, stride, top, x1, x2))
  683. top--;
  684. while ((top < bottom) && IsEmptyLine(pixels, stride, top, x1, x2))
  685. top++;
  686. // ピクセルデータがある下端を走査
  687. while ((bottom < height) && !IsEmptyLine(pixels, stride, bottom, x1, x2))
  688. bottom++;
  689. bottom = Math.Min(bottom, height- 1);
  690. while ((top <= bottom) && IsEmptyLine(pixels, stride, bottom, x1, x2))
  691. bottom--;
  692. // スペースキャラクターだった(全てが透明色の文字)
  693. if (bottom < top)
  694. {
  695. top = bottom = 0;
  696. }
  697. // グリフサイズ調整
  698. bounds.Y = top;
  699. bounds.Height = bottom - top + 1;
  700. return bounds;
  701. }
  702. /// <summary>
  703. /// 画像の指定された列に透明色以外のピクセルが存在するか?
  704. /// </summary>
  705. static bool IsEmptyColumn(uint[] pixels, int stride, int x, int y1, int y2)
  706. {
  707. var idx = y1 * stride + x;
  708. for (int y = y1; y < y2; ++y, idx += stride)
  709. {
  710. if (pixels[idx] != TransparentPixel)
  711. return false;
  712. }
  713. return true;
  714. }
  715. /// <summary>
  716. /// 画像の指定された行に透明色以外のピクセルが存在するか?
  717. /// </summary>
  718. static bool IsEmptyLine(uint[] pixels, int stride, int y, int x1, int x2)
  719. {
  720. var idx = y * stride + x1;
  721. for (int x = x1; x < x2; ++x, ++idx)
  722. {
  723. if (pixels[idx] != TransparentPixel)
  724. return false;
  725. }
  726. return true;
  727. }
  728. #endregion
  729. #region その他のヘルパーメソッド
  730. /// <summary>
  731. /// XNAのColor構造体からWPFのColorへ変換する
  732. /// </summary>
  733. static System.Windows.Media.Color ToWpfColor(Color color)
  734. {
  735. return
  736. System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B);
  737. }
  738. /// <summary>
  739. /// XNAのRectangle構造体からInt32Rectへ変換する
  740. /// </summary>
  741. static Int32Rect ToInt32Rect(Rectangle rc)
  742. {
  743. return new Int32Rect(rc.X, rc.Y, rc.Width, rc.Height);
  744. }
  745. #endregion
  746. #region 定数
  747. // WPFのDIU(Device Independent Unit)
  748. const double WpfDiu = 96;
  749. // 透明色
  750. const uint TransparentPixel = 0;
  751. #endregion
  752. #region プライベートフィールド
  753. // 処理中のFontDescription
  754. FontDescription input;
  755. // 出力先
  756. WpfSpriteFontContent fontContent;
  757. // フォントサイズ(DIU)
  758. float fontSize;
  759. // WPFフォント情報
  760. Typeface typeface;
  761. GlyphTypeface glyphTypeface;
  762. // テキスト描画用ブラシ
  763. Brush textBrush;
  764. // アウトライン描画用ペン
  765. Pen outlinePen;
  766. // 一文字を描画するためのレンダーターゲット
  767. RenderTargetBitmap renderTarget;
  768. // 文字描画用のDrawingVisual
  769. DrawingVisual drawingVisual;
  770. #endregion
  771. }
  772. }