TextRendering.cs
// 
// このコードは、DioDocs for Imaging のサンプルの一部として提供されています。
// © MESCIUS inc. All rights reserved.
// 
using System;
using System.IO;
using System.Drawing;
using GrapeCity.Documents.Imaging;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Drawing;
using GCTEXT = GrapeCity.Documents.Text;
using GCDRAW = GrapeCity.Documents.Drawing;

namespace DsImagingWeb.Demos
{
    // DsImaging におけるテキスト描画の基本を説明するサンプルです。
    // 主なアプローチは次の 2 つです。
    // - MeasureString と DrawString の組み合わせを使用する方法
    // - TextLayout を直接使用する方法
    // 前者はシンプルなケースでは扱いやすい方法です。
    // 一方、後者(TextLayout を使用する方法)はより高機能で、
    // 一般的にパフォーマンスにも優れています。
    // 詳細は、以下のコード内のコメントを参照してください。
    public class TextRendering
    {
        public GcBitmap GenerateImage(Size pixelSize, float dpi, bool opaque, string[] sampleParams = null)
        {
            var bmp = new GcBitmap(pixelSize.Width, pixelSize.Height, true, dpi, dpi);
            var Inch = dpi;
            const float fontSize = 14;
            using (var g = bmp.CreateGraphics(Color.White))
            {
                // TextFormatクラスは、フォントやその他の文字書式を指定するために、
                // すべてのDsImagingテキストレンダリングで使用されます。
                var tf = new TextFormat()
                {
                    Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "NotoSerif-Regular.ttf")),
                    FontSize = fontSize
                };

                // 1.
                // 文字列が利用可能なスペースに収まることが100%確実な場合に、
                // ページ上の任意の場所に短い文字列をレンダリングする最も簡単な方法は、
                // 文字列を描画するポイントのみを受け取るGcGraphics.DrawString()のオーバーロードを使用することです。
                g.DrawString(
                    "1. Test string. Please read the extensive comments in this sample's code.\r\n" +
                    "(Note that line breaks are allowed even in the simplest DrawString overload.)",
                    tf, new PointF(Inch, Inch));

                // 2.
                // 代わりに矩形を受け取り、配置や折り返しのオプションを指定できる別のオーバーロードも利用可能です。
                // こちらの方が少し柔軟性が高くなります。
                g.DrawString(
                    "2. A longer test string which will probably need more than the allocated " +
                    "4 inches so quite possibly will wrap to show that DrawString can do that.",
                    tf,
                    new RectangleF(Inch, Inch * 2, Inch * 4, Inch), // レイアウト矩形
                                                                    // 残りの3つの引数はオプションです。ここでは説明のためにデフォルト値を渡しています。
                    TextAlignment.Leading,   // 先頭(LTR言語では左)のテキスト配置
                    ParagraphAlignment.Near, // 近接(上から下へのフローでは上)の段落配置
                    true); // ワードラップ

                // 3.
                // DrawStringを補完するものとして、MeasureString()メソッドが利用可能です。
                // (いくつかの異なるオーバーロードがあります)。テキストレイアウトをより詳細に制御する必要がある場合に、
                // DrawStringと組み合わせて使用できます。
                const string tstr3 = "3. Test string to demo MeasureString() used with DrawString().";

                SizeF layoutSize = new SizeF(Inch * 3, Inch * 0.8f); // 利用可能なサイズ
                SizeF s = g.MeasureString(tstr3, tf, layoutSize, out int fitCharCount);
                // 渡されたサイズを赤色で、計測されたサイズを青色で表示し、
                // 返されたサイズを境界として文字列を描画します。
                PointF pt = new PointF(Inch, Inch * 3);
                g.DrawRectangle(new RectangleF(pt, layoutSize), Color.Red);
                g.DrawRectangle(new RectangleF(pt, s), Color.Blue);
                g.DrawString(tstr3, tf, new RectangleF(pt, s));

                // 4.
                // テキストをレンダリングするための、より強力でパフォーマンスに優れた方法は、TextLayoutを使用することです。
                // (DrawStringやMeasureString内部でもTextLayoutが使用されているため、
                // 直接TextLayoutを使用すると、実質的に作業を半分に減らすことができます。)
                // TextLayoutインスタンスは、同じ段落書式を持つ1つ以上のテキスト段落を表します。
                // (文字書式は異なる場合があります。詳細はMultiFormattedTextを参照してください)。
                var tl = g.CreateTextLayout();
                // テキストを追加するには、Append() または AppendLine() メソッドを使用します。
                tl.Append("4. First test string added to TextLayout. ", tf);
                tl.Append("Second test string added to TextLayout, continuing the same paragraph. ", tf);
                tl.AppendLine(); // 改行を追加し、実質的に新しい段落を開始します。
                tl.Append("Third test string added to TextLayout, a new paragraph. ", tf);
                tl.Append("Fourth test string, with a different char formatting. ",
                    new TextFormat(tf)
                    {
                        Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "NotoSerif-BoldItalic.ttf")),
                        FontSize = fontSize,
                        FontBold = true,
                        FontItalic = true,
                        ForeColor = Color.DarkSeaGreen,
                    });
                // 明示的なTextFormatを指定せずにTextLayoutにテキストを追加することもできます。
                tl.Append("Fifth test string, using the TextLayout's default format.");
                // ただし、その場合は少なくともTextLayoutのDefaultFormatにFontを指定する必要があります。
                // 指定しない場合、後述のPerformLayoutが失敗します。
                tl.DefaultFormat.Font = GCTEXT.Font.FromFile(Path.Combine("Resources", "Fonts", "NotoSerif-Italic.ttf"));
                tl.DefaultFormat.FontSize = fontSize;

                // 最大利用可能サイズなどのレイアウトを指定します。
                // ここでは最大幅のみを指定していますが、他にも多くのパラメータを設定できます。
                tl.MaxWidth = g.Width - Inch * 2;
                // 段落の書式も設定できます。ここでは一行目のインデント、
                // 段落間の間隔、および行間隔を設定しています。
                tl.FirstLineIndent = Inch * 0.5f;
                tl.ParagraphSpacing = Inch * 0.05f;
                tl.LineSpacingScaleFactor = 0.8f;

                // すべてのテキストが追加され、レイアウトオプションが指定されたら、
                // TextLayoutはテキストのレンダリングに必要なグリフを計算し、レイアウトを実行する必要があります。
                // これは単一の呼び出しで行うことができます。
                tl.PerformLayout(true);

                // これでページ上に描画できます。
                pt = new PointF(Inch, Inch * 4);
                g.DrawTextLayout(tl, pt);
                // TextLayoutは、計測された境界を含むテキストに関する情報などを提供します。
                // ここではバウンディングボックスをオレンジレッドで描画します。
                g.DrawRectangle(new RectangleF(pt, tl.ContentRectangle.Size), Color.OrangeRed);

                // 5.
                // TextLayoutは、別の段落を描画するために再利用できます。これは、
                // 同じ段落書式で異なるテキストをレンダリングする必要がある場合に便利です。
                // Clear()を呼び出すとテキストは削除されますが、段落書式は保持されます。
                tl.Clear();
                tl.Append("5. This is text rendered re-using the same TextLayout. ");
                tl.Append("More text added to TextLayout being re-used, continuing the same paragraph. ", tf);
                tl.Append("And finally, some more text added.", tf);
                // グリフを計算しレイアウトを実行するための必須の呼び出しです。
                tl.PerformLayout(true);
                // テキストをレンダリングします。
                g.DrawTextLayout(tl, new PointF(Inch, Inch * 5));
                // 画像全体の周囲に境界線を描画します。
                g.DrawRectangle(new RectangleF(0, 0, bmp.Width, bmp.Height), Color.DarkSlateBlue, 4);
            }

            return bmp;
        }
    }
}