DioDocs for PDF
複雑なグラフィックレイアウト
機能 > 複雑なグラフィックレイアウト

DioDocs for PDF では、複雑なグラフィックス、テキスト、およびイメージを描画するには、レイアウトエンジンと描画領域の間の媒体として機能するGrapeCity.Documents.Layout.Composition 名前空間の SurfaceLayerViewSpace、および Visual クラスを使用します。また、これらのクラスを使用すると、描画されたグラフィックスの Z オーダーとクリッピングをカスタマイズすることもできます。

Surface は、Composition エンジンの主クラスであり、LayoutHost(レイアウト エンジンのルート オブジェクト) と 1 つ以上のビュー (レイヤー) が含まれています。 レイヤーは、ビジュアル (描画可能な要素) とネストされたレイヤーで構成されます。Surface クラスの Render メソッドは、LayoutHost クラスの PerformLayout メソッドを呼び出して領域のレイアウトを計算し後、指定された GcGraphics オブジェクト上に最下位から最上位まですべてのレイヤー (ネストされたレイヤーを含む) を描画します。

レイヤーには、Layer クラス オブジェクトと View クラス オブジェクト (Layer オブジェクトから派生) の 2 つの種類があります。View オブジェクトは、独自の変換行列を持つ異なる座標系を表す LayoutView オブジェクトをカプセル化します。 Layer オブジェクトは、ビジュアル、ネストされたレイヤー、および可能なクリッピング領域の独自のリストを備えた軽量のビューとして機能します。Surface オブジェクトはビューのみを作成でき、レイヤーは作成できません。 ただし、各 View オブジェクト (Layer オブジェクトも同様) は、ネストされた Layer とネストされた View の両方を作成できます。領域上に少なくとも 1 つのビューを作成し、そのビューを使用してネストされたレイヤー (同じ変換) またはネストされたビュー(異なる変換行列)を作成する必要があります。

レイヤーにはVisualsとSpacesが含まれます。 Space オブジェクトは、他の要素のレイアウトに影響する可能性がある LayoutRect を表しますが、それ自体が描画されることはありません。 Spacesオブジェクトは、視覚要素の Z 階層の一部ではありません。Visual クラスは Space クラスから派生します。 Visual クラスは、ターゲット GcGraphics 上に描画される要素を表します。Surface クラスの Render メソッドは、Visible クラスと Layer クラスの特別な Draw デリゲート(Visible プロパティが True に設定されている)を呼び出し、パラメータとしてGcGraphics オブジェクトと現在の項目 (Layer または Visual) を渡します。

テキストを含む複雑なグラフィックを描画する方法については、次のサンプルコードを参照してください。

C#
コードのコピー
// GcPdfDocument を初期化します。
var doc = new GcPdfDocument();

// PDF ドキュメントにページを追加します。
float pageW = 400;
float pageH = 300;
var page = doc.Pages.Add(new SizeF(pageW, pageH));

// テキスト書式を設定します。
var fmt = new TextFormat
{
    FontName = "Segoe UI",
    FontSize = 12f,
    ForeColor = Color.White
};

// Surfaceを初期化します。
var sf = new Surface();

// LayoutViewを作成します。
var view = sf.CreateView(300, 200);

// 最初の図を作成します。
var fig1 = view.CreateVisual();
fig1.LayoutRect.AnchorTopLeft(null, 10, 10, 300, 200);
fig1.Draw = (g, v) => {
    g.FillEllipse(v.AsRectF(), Color.LightSalmon);
    g.DrawString("1", fmt, new PointF(50, 50));
};

// 2 番目の図を作成します。
var fig2 = view.CreateVisual((g, v) => {
    g.FillRoundRect(v.AsRectF(), 20, Color.MediumAquamarine);
    g.DrawString("2", fmt, new PointF(v.Width - 35, v.Height - 45));
});
fig2.LayoutRect.AnchorTopLeft(null, 50, 50, 300, 200);

// 3 番目の図を作成します。
view.CreateVisual((g, v) => {
    g.FillRectangle(v.AsRectF(), Color.CornflowerBlue);
    g.DrawString("3", fmt, new PointF(v.Width - 27, v.Height - 35));
}).LayoutRect.AnchorTopLeft(null, 90, 90, 300, 200);

// 1 番目と 2 番目の数字を前面に移動します。
fig2.BringToFront();
fig1.BringToFront();

// GcPdfGraphics を初期化します。
var g = page.Graphics;

// Surfaceを描画します。
sf.Render(g);

// PDF ドキュメントを保存します。
doc.Save("Composition.pdf");

クリッピング

Layer オブジェクトで指定されたクリッピングは、レイヤーのビジュアルとネストされたレイヤーに適用されます。 Layer クラスには、クリッピングを定義できる ClipRect と CreateClipRegion があります。これら 2 つのプロパティのうち 1 つだけを一度に指定することも、両方を指定することもできます。動作は次の 3 つの場合で異なります。

  1. ClipRect のみが指定されている場合、LayoutRect 値によってクリッピングが定義されます。 LayoutRect は同じ Surface 上の任意の View にすることができ、その変換を対応する View に適用できることに注意してください。
    C#
    コードのコピー
    // GcPdfDocument を初期化します。
    var doc = new GcPdfDocument();
    
    // PDF ドキュメントにページを追加します。
    float pageW = 400;
    float pageH = 300;
    var page = doc.Pages.Add(new SizeF(pageW, pageH));
    
    // Surfaceを初期化します。
    var sf = new Surface();
    
    // 最初の LayoutView を作成します。
    var view = sf.CreateView(1, 1);
    
    // 最初のサブレイヤーを作成します。
    var nestedLayer1 = view.CreateSubLayer();
    
    // 最初の図を作成します。
    var rect = nestedLayer1.CreateVisual((g, v) => {
        g.DrawRectangle(v.AsRectF(), new Pen(Color.Magenta, 1));
        g.DrawString("Rectangle 1", new TextFormat
        {
            FontName = "Segoe UI",
            FontSize = 16f,
            ForeColor = Color.Magenta
        }, new PointF(120, 90));
    });
    rect.LayoutRect.AnchorTopLeft(null, 20, 20, 300, 200);
    
    // 2 番目のサブレイヤーを作成します。
    var nestedLayer2 = view.CreateSubLayer();
    
    // 2 番目の図を作成します。
    nestedLayer2.CreateVisual((g, v) => {
        g.FillRectangle(v.AsRectF(), new HatchBrush(HatchStyle.Weave)
        {
            BackColor = Color.White,
            ForeColor = Color.RoyalBlue
        });
    }).LayoutRect.AnchorExact(rect.LayoutRect);
    
    // 2 番目の LayoutView を作成します。
    var view2 = sf.CreateView(1, 1).Translate(120, 30).Rotate(30);
    
    // クリッピング領域を作成します。
    var clipRect = view2.CreateVisual((g, v) => {
        g.DrawRectangle(v.AsRectF(), Color.Green, 1, DashStyle.Dash);
    }).LayoutRect;
    clipRect.AnchorTopLeft(null, 0, 0, 300, 100);
    
    // クリッピング領域を設定します。
    nestedLayer2.ClipRect = clipRect;
    
    // 最初のサブレイヤーを前面に表示します。
    nestedLayer1.BringToFront();
    
    // GcPdfGraphics を初期化します。
    var g = page.Graphics;
    
    // Surfaceを描画します。
    sf.Render(g);
    
    // PDF ドキュメントを保存します。
    doc.Save("Clipping.pdf");
    

  2. CreateClipRegion デリゲートのみが指定されている場合、GcGraphics.PushClip(clipRegion) は、レイヤーを描画する前に、デリゲートによって返されたクリッピングの領域をグラフィックスに適用します。この場合、クリッピングの領域は、追加の変換を行わずに、レイヤー独自の座標系で定義されます。 CreateClipRegion デリゲートを使用すると、長方形以外のクリッピング領域を設定できます。 任意のパス、そのパスに基づいてクリッピング領域を作成して、デリゲートから返すことができます。
  3. ClipRect プロパティと CreateClipRegion プロパティの両方が指定されている場合、クリッピング領域は ClipRect プロパティで指定された LayoutRect の座標系で定義されます。LayoutRect の左上隅が原点となり、軸は側面に沿って右と下に向けられます。上記の1番名の場合と同様に、LayoutRect は任意のViewからのものであり、その変換はクリッピングされるレイヤーの変換に依存しません。その後、返されたクリッピング領域は、CreateClipRegion デリゲートを呼び出すことで、ClipRect で定義された座標系に適用されます。クリッピング領域を適用した後、レイヤー上のオブジェクトはレイヤーの座標系で描画されますが、クリッピングは ClipRect と CreateClipRegion によって変換されたままになります。 この方法により、複雑なクリッピングを簡単に作成できます。たとえば、回転された楕円クリッピングを作成する場合は、CreateClipRegion デリゲートから回転されていない楕円領域を返し、ClipRect で定義された変換を使用して回転できます。
    C#
    コードのコピー
    // GcPdfDocument を初期化します。
    var doc = new GcPdfDocument();
    
    // PDF ドキュメントにページを追加します。
    float pageW = 600;
    float pageH = 600;
    var page = doc.Pages.Add(new SizeF(pageW, pageH));
    
    // Surfaceを初期化します。
    var sf = new Surface();
    
    // 最初の LayoutView を作成します。
    var view = sf.CreateView(1, 1);
    
    // 最初のサブレイヤーを作成します。
    var nestedLayer1 = view.CreateSubLayer();
    
    // 最初の図を作成します。
    var rect = nestedLayer1.CreateVisual((g, v) =>
    {
        g.DrawRectangle(v.AsRectF(), new Pen(Color.MediumAquamarine, 1));
        g.DrawString("Rectangle 2", new TextFormat
        {
            FontName = "Segoe UI",
            FontSize = 16f,
            ForeColor = Color.MediumAquamarine
        }, new PointF(120, 90));
    });
    rect.LayoutRect.AnchorTopLeft(null, 10, 40, 300, 200);
    
    // 2 番目のサブレイヤーを作成します。
    var nestedLayer2 = view.CreateSubLayer();
    
    // 2 番目の図を作成します。
    nestedLayer2.CreateVisual((g, v) =>
    {
        var lgb = new LinearGradientBrush(Color.Blue, Color.Red);
        g.FillRectangle(v.AsRectF(), lgb);
    }).LayoutRect.AnchorExact(rect.LayoutRect);
    
    // 2 番目の LayoutView を作成します。
    var view2 = sf.CreateView(1, 1).Translate(10, 140).Rotate(-20);
    var clipRect = view2.CreateVisual((g, v) =>
    {
        g.DrawRectangle(v.AsRectF(), Color.Salmon, 1, DashStyle.Dash);
    }).LayoutRect;
    clipRect.AnchorTopLeft(null, 0, 0, 350, 90);
    
    // クリッピング領域を作成します。
    nestedLayer2.ClipRect = clipRect;
    nestedLayer2.CreateClipRegion = (g, layer) =>
    {
        var path = (GcBitmapGraphics.Path)g.CreatePath();
        var rc = layer.ClipRect.AsRectF();
        rc.Inflate(0, 20);
        path.PathBuilder.AddFigure(new EllipticFigure(rc));
        return g.CreateClipRegion(path);
    };
    nestedLayer2.SendToBack();
    
    // GcBitmapを初期化します。
    using var bmp = new GcBitmap(380 * 2, 230 * 2, true);
    using (var g = bmp.CreateGraphics(Color.White))
    {
        g.Transform = Matrix3x2.CreateScale(2);
    
        // Surfaceを描画します。
        sf.Render(g);
    }
    
    // GcPdfGraphics を初期化します。
    var gr = page.Graphics;
    
    // 画像を描画します。
    var image = bmp;
    gr.DrawImage(image, new RectangleF(30.6F, 30.7F, 40.8F, 100.9F), null, ImageAlign.Default);
    
    // PDF ドキュメントを保存します。
    doc.Save("ClippingEllipticalRegion.pdf");
    

アンカー ポイント

Layer クラスには、LayoutRect に関連付けられていない Visual を作成する CreateVisual メソッドがあります。 Visualの位置とサイズは、1 つまたは複数のアンカーポイントに基づいて計算されます。

Visual クラスの AnchorPoints プロパティに割り当てられたアンカーポイントは、Visual クラスの Draw デリゲートを実行する前に、View 座標系で再計算され、Visual クラスの Points プロパティに保存されます。

アンカー ポイントを基準にして長方形を描画する方法については、次のサンプルコードを参照してください。

C#
コードのコピー
// GcPdfDocument を初期化します。
var doc = new GcPdfDocument();

// PDF ドキュメントにページを追加します。
float pageW = 400;
float pageH = 300;
var page = doc.Pages.Add(new SizeF(pageW, pageH));

// Surfaceを初期化します。
var sf = new Surface();

// LayoutViewを作成します。
var view = sf.CreateView(pageW, pageH);

// 余白の長方形を作成します。
var marginRect = view.CreateVisual((g, v) => {
    g.DrawRectangle(v.AsRectF(), new Pen(Color.Green));
}).LayoutRect;
marginRect.AnchorDeflate(null, 10);

// ポイントを作成します。
var ap1 = marginRect.CreatePoint(0.25f, 0.25f);
var ap2 = marginRect.CreatePoint(0.75f, 0.25f);
var ap3 = marginRect.CreatePoint(0.75f, 0.75f);
var ap4 = marginRect.CreatePoint(0.25f, 0.75f);

var bluePen = new Pen(Color.CornflowerBlue);

// アンカーポイントを作成します。
view.CreateVisual(new AnchorPoint[] { ap1 }, DrawAP);
view.CreateVisual(new AnchorPoint[] { ap2 }, DrawAP);
view.CreateVisual(new AnchorPoint[] { ap3 }, DrawAP);
view.CreateVisual(new AnchorPoint[] { ap4 }, DrawAP);

// アンカーポイントを描画します。
void DrawAP(GcGraphics g, Visual v)
{
    var pt = v.Points[0];
    g.DrawRectangle(new RectangleF(pt.X - 5, pt.Y - 5, 10, 10), bluePen);
}

// アンカーポイントを使用して多角形を描画します。
view.CreateVisual(new AnchorPoint[] { ap1, ap2, ap3, ap4 },
(g, v) => {
    g.DrawPolygon(v.Points, new Pen(Color.Red));
});

// GcPdfGraphics を初期化します。
var g = page.Graphics;

// Surfaceを描画します。
sf.Render(g);

// PDF ドキュメントを保存します。
doc.Save("AnchorPoints.pdf");

等高線

等高線はアンカーポイントと同様に視覚化できます。 Visual クラスの Contour プロパティと AnchorPoints プロパティは相互に排他的で、両方のプロパティを空ではない値に割り当てると、Surfaceを描画するときに例外が発生します。等高線ポイントは、Draw デリゲートを実行する前に、Visual クラスの Points プロパティを使用して通常のポイントに変換されます。Draw デリゲートから輪郭を描画するには、GcGraphics クラスの DrawPolygon などのメソッドを使用します。

いくつかのカーブがあり、それらの間のスペースを埋める必要がある場合があります。この場合、Layer クラスの Draw デリゲートを使用して、複数の等高線間のスペースを埋めることができます。 各等高線は、同じビューまたはレイヤー上の個別のVisualオブジェクトに関連付けることができます。 Draw デリゲートからすべてのビジュアルの配列を取得するには、Layer クラスの GetVisuals メソッドを使用します。Visual クラスの Points プロパティの値を使用することで、グラフィックスパスを作成し、複数の等高線を個別の図形として追加できます。

複数の等高線を描画し、複数の長方形を描画してそれらの間のスペースを埋める方法については、次のサンプルコードを参照してください。

C#
コードのコピー
// GcPdfDocument を初期化します。
var doc = new GcPdfDocument();

// PDF ドキュメントにページを追加します。
float pageW = 600;
float pageH = 570;
var page = doc.Pages.Add(new SizeF(pageW, pageH));

// Surfaceを初期化します。
var sf = new Surface();

// 棒等高線を作成します。
var c1 = CreateBarContour(sf);

// ドーナツ等高線図を作成します。
var (c2, c3) = CreateDonutContours(sf);

// 最初のLayoutViewを作成します。
var view = sf.CreateView(1, 1, (g, l) =>
{
    using var path = g.CreatePath();
    path.SetFillMode(FillMode.Winding);
    var figures = l.GetVisuals();
    for (int i = 0; i < figures.Length; i++)
    {
        var pts = figures[i].Points;
        path.BeginFigure(pts[0]);
        for (int j = 1; j < pts.Length; j++)
            path.AddLine(pts[j]);
        path.EndFigure(FigureEnd.Closed);
    }
    g.FillPath(path, Color.LemonChiffon);
    g.DrawPath(path, Color.Tomato, 1f);
});

// ビジュアルを作成します。
view.CreateVisual(c1, false);
view.CreateVisual(c2, false);
view.CreateVisual(c3, false);

// 2 番目の LayoutView を作成します。
var view2 = sf.CreateView(1, 1).Translate(-90, -20).Skew(30, 0).Rotate(20);

// 長方形を描画します。
float top = 0f;
var pen = new Pen(Color.LightSeaGreen);
for (int i = 0; i < 22; i++)
{
    DrawRects(view2, pen, top, c1, c2, c3);
    top += 20f;
}

// GcPdfGraphics を初期化します。
var g = page.Graphics;

// Surfaceを描画します。
sf.Render(g);

// PDF ドキュメントを保存します。
doc.Save("Contours.pdf");

// 棒等高線を作成します。
static Contour CreateBarContour(Surface surf)
{
    // 棒等高線のレイアウト ビューを作成します。
    var fig = surf.CreateView(1, 1).Translate(160, 80).Rotate(-30);

    // 長方形の空間を作成します。
    var sp = fig.CreateSpace().LayoutRect;
    sp.AnchorTopLeft(null, 0f, 0f, 30, 500);

    // 等高線を作成します。
    var c = fig.LayoutView.CreateContour();

    // アンカーするポイントを作成します。
    c.AddPoints(new AnchorPoint[]
    {
        sp.CreatePoint(0, 0),
        sp.CreatePoint(1, 0),
        sp.CreatePoint(1, 1),
        sp.CreatePoint(0, 1)
    });
    return c;
}

// ドーナツ等高線を作成します。
static (Contour, Contour) CreateDonutContours(Surface surf)
{
    // ドーナツ等高線のレイアウト ビューを作成します。
    var fig = surf.CreateView(1, 1).Translate(30, 100).Skew(20, 0);

    // ドーナツ等高線の寸法を設定します。
    float rMax = 150;
    float xOffsetMax = 200;
    float yOffsetMax = 200;
    float rMin = 100;
    float xOffsetMin = 230;
    float yOffsetMin = 210;
    int nMax = 100;
    int nMin = 70;

    var maxPoints = new List<AnchorPoint>(nMax);
    var minPoints = new List<AnchorPoint>(nMin);
    double deltaMax = Math.PI * 2 / nMax;
    double deltaMin = Math.PI * (-2) / nMin;
    var lv = fig.LayoutView;

    for (int i = 0; i < nMax; i++)
    {
        double alpha = deltaMax * i;
        float x = (float)(Math.Cos(alpha) * rMax + xOffsetMax);
        float y = (float)(Math.Sin(alpha) * rMax + yOffsetMax);
        maxPoints.Add(lv.CreatePoint(0f, 0f, x, y));
    }
    for (int i = 0; i < nMin; i++)
    {
        double alpha = deltaMin * i;
        float x = (float)(Math.Cos(alpha) * rMin + xOffsetMin);
        float y = (float)(Math.Sin(alpha) * rMin + yOffsetMin);
        minPoints.Add(lv.CreatePoint(0f, 0f, x, y));
    }

    // 等高線を作成します。
    var c1 = lv.CreateContour();
    c1.AddPoints(maxPoints);

    var c2 = lv.CreateContour();
    c2.AddPoints(minPoints);

    return (c1, c2);
}

// 長方形を描画します。
static void DrawRects(View view, Pen pen, float top, params Contour[] contours)
{
    LayoutRect? rPrev = null;
    while (true)
    {
        var r0 = view.CreateVisual((g, v) => {
            g.DrawRectangle(v.AsRectF(), pen);
        }).LayoutRect;
        if (rPrev is null)
            r0.AnchorTopLeft(null, top, 100);
        else
        {
            r0.SetTop(null, AnchorParam.Top, top);
            r0.SetLeft(rPrev, AnchorParam.Right);
        }
        r0.SetHeight(16);
        r0.AppendMaxRight(null, AnchorParam.Left, 500);

        var r1 = view.CreateSpace().LayoutRect;
        r1.SetTop(null, AnchorParam.Top, top);
        r1.SetHeight(16);
        r1.SetLeft(r0, AnchorParam.Right);
        r1.AppendMaxRight(null, AnchorParam.Left, 500);

        foreach (var c in contours)
        {
            r0.AppendMaxRight(c, ContourPosition.FirstInOutside);
            r1.AppendMaxRight(c, ContourPosition.NextOutOutside);
        }
        view.Surface.PerformLayout();
        if (r1.Width > 0f)
            rPrev = r1;
        else
        {
            ((Space)r1.Tag).Detach();
            break;
        }
    }
}