Shapes.cs
// 
// このコードは、DioDocs for PDF のサンプルの一部として提供されています。
// Copyright (c) GrapeCity inc. All rights reserved.
// 
using System;
using System.IO;
using System.Drawing;
using System.Numerics;
using System.Linq;
using GrapeCity.Documents.Pdf;
using GrapeCity.Documents.Text;
using GrapeCity.Documents.Drawing;

namespace GcPdfWeb.Samples
{
    // GcPdf でさまざまな図形をどのように描くことができるかを示します。
    // シンプルなシェイプを組み合わせてより複雑な形状を生成する方法を示します。
    // 単純なグラフィックス変換を使用していくつかの図形を描画します。
    public class Shapes
    {
        // 多角形とその下にキャプションを描画するヘルパーメソッドです。
        // 実際の描画を行わずに位置を計算するためにも使用できます。
        // startAngle は(1,0)から時計回りの最初の点です。
        private PointF[] DrawPolygon(GcGraphics g, PointF center, float r, int n, float startAngle, Pen pen, string caption = null)
        {
            PointF[] pts = new PointF[n];
            for (int i = 0; i < n; ++i)
                pts[i] = new PointF(center.X + (float)(r * Math.Cos(startAngle + 2 * Math.PI * i / n)), center.Y + (float)(r * Math.Sin(startAngle + 2 * Math.PI * i / n)));
            if (pen != null)
                g.DrawPolygon(pts, pen);
            if (!string.IsNullOrEmpty(caption))
                DrawCaption(g, center, r, caption);
            return pts;
        }
        // 図形の下にキャプションを描画するヘルパーメソッドです。
        private void DrawCaption(GcGraphics g, PointF center, float r, string caption)
        {
            g.DrawString(caption,
                new TextFormat() { Font = GcPdfWeb.Samples.Common.Util.getFont(), FontSize = 10, },
                new RectangleF(center.X - r, center.Y + r, r * 2, 24),
                TextAlignment.Center, ParagraphAlignment.Center, false);
        }
        // メインエントリポイントです。
        public void CreatePDF(Stream stream)
        {
            var doc = new GcPdfDocument(){FontEmbedMode = FontEmbedMode.EmbedSubset};
            var page = doc.Pages.Add();
            var g = page.Graphics;
            // ドキュメントのヘッダーです。
            g.DrawString("図形",
                new TextFormat() { Font = GcPdfWeb.Samples.Common.Util.getFont(), FontBold = true, FontSize = 14, Underline = true, },
                new RectangleF(PointF.Empty, new SizeF(page.Size.Width, 44)),
                TextAlignment.Center, ParagraphAlignment.Far);
            // 図形を描画するために使用するペンです。
            var pen = new Pen(Color.Orange, 1);
            pen.LineJoin = PenLineJoin.Round;
            int fill = 100; // 表面を塗りつぶすアルファ値

            // レイアウト用グリッドを設定します。
            var grid = new
            {
                Cols = 3,
                Rows = 5,
                MarginX = 72,
                MarginY = 36,
                Radius = 36,
                StepX = (page.Size.Width - 144) / 3,
                StepY = (page.Size.Height - 72) / 5,
            };

            // 次の図の中心の挿入位置です。
            PointF startIp = new PointF(grid.MarginX + grid.StepX / 2, grid.MarginY + grid.StepY / 2 + 10);
            PointF ip = startIp;
            // レイアウトグリッドを表示するためのデバッグコードです。
            /* 
            var ipp = ip;
            for (int i = 0; i < grid.Cols; ++i)
            {
                ipp.Y = ip.Y;
                for (int j = 0; j < grid.Rows; ++j)
                {
                    g.DrawRectangle(new RectangleF(ipp.X - grid.Radius, ipp.Y - grid.Radius, grid.Radius * 2, grid.Radius * 2), Color.LightGreen, 0.5f);
                    ipp.Y += grid.StepY;
                }
                ipp.X += grid.StepX;
            }
            */

            // 円。
            g.DrawEllipse(new RectangleF(ip.X - grid.Radius, ip.Y - grid.Radius, grid.Radius * 2, grid.Radius * 2), pen);
            DrawCaption(g, ip, grid.Radius, "円");
            ip.X += grid.StepX;

            // 楕円。
            g.DrawEllipse(new RectangleF(ip.X - grid.Radius * 1.4f, ip.Y - grid.Radius / 2, grid.Radius * 2 * 1.4f, grid.Radius), pen);
            DrawCaption(g, ip, grid.Radius, "楕円");
            ip.X += grid.StepX;

            // 円柱。
            float radX = grid.Radius / 1.4f;
            float radY = grid.Radius / 6;
            float height = grid.Radius * 1.8f;
            g.DrawEllipse(new RectangleF(ip.X - radX, ip.Y - height / 2, radX * 2, radY * 2), pen);
            g.FillEllipse(new RectangleF(ip.X - radX, ip.Y + height / 2 - radY * 2, radX * 2, radY * 2), Color.FromArgb(fill, pen.Color));
            g.DrawEllipse(new RectangleF(ip.X - radX, ip.Y + height / 2 - radY * 2, radX * 2, radY * 2), pen);
            g.DrawLine(new PointF(ip.X - radX, ip.Y - height / 2 + radY), new PointF(ip.X - radX, ip.Y + height / 2 - radY), pen);
            g.DrawLine(new PointF(ip.X + radX, ip.Y - height / 2 + radY), new PointF(ip.X + radX, ip.Y + height / 2 - radY), pen);
            DrawCaption(g, ip, grid.Radius, "円柱");
            ip.X = startIp.X;
            ip.Y += grid.StepY;
            pen.Color = Color.Indigo;

            // 正方形。
            DrawPolygon(g, ip, grid.Radius, 4, (float)-Math.PI / 4, pen, "正方形");
            ip.X += grid.StepX;

            // 長方形。
            float rectQx = 1.4f;
            float rectQy = 0.6f;
            var rect = new RectangleF(ip.X - grid.Radius * rectQx, ip.Y - grid.Radius * rectQy, grid.Radius * 2 * rectQx, grid.Radius * 2 * rectQy);
            g.DrawRectangle(rect, pen);
            DrawCaption(g, ip, grid.Radius, "長方形");
            ip.X += grid.StepX;

            // 立方体。
            float cubex = 6;
            var cubePtsFar = DrawPolygon(g, new PointF(ip.X - cubex, ip.Y - cubex), grid.Radius, 4, (float)-Math.PI / 4, pen);
            var cubePtsNear = DrawPolygon(g, new PointF(ip.X + cubex, ip.Y + cubex), grid.Radius, 4, (float)-Math.PI / 4, pen);
            g.DrawLine(cubePtsFar[0], cubePtsNear[0], pen);
            g.DrawLine(cubePtsFar[1], cubePtsNear[1], pen);
            g.DrawLine(cubePtsFar[2], cubePtsNear[2], pen);
            g.DrawLine(cubePtsFar[3], cubePtsNear[3], pen);
            g.FillPolygon(new PointF[] { cubePtsFar[1], cubePtsFar[2], cubePtsNear[2], cubePtsNear[1], }, Color.FromArgb(fill, pen.Color));
            DrawCaption(g, ip, grid.Radius, "立方体");
            ip.X = startIp.X;
            ip.Y += grid.StepY;
            pen.Color = Color.DarkGreen;

            // 五角形。
            DrawPolygon(g, ip, grid.Radius, 5, (float)-Math.PI / 2, pen, "五角形");
            ip.X += grid.StepX;

            // 六角形。
            // サンプルのために、変換を適用して六角形をより幅広く短くします。
            g.Transform = Matrix3x2.CreateScale(1.4f, 0.8f) * Matrix3x2.CreateTranslation(ip.X, ip.Y);
            DrawPolygon(g, PointF.Empty, grid.Radius, 6, 0, pen, null);
            g.Transform = Matrix3x2.Identity;
            DrawCaption(g, ip, grid.Radius, "六角形");
            ip.X += grid.StepX;

            // 八角形。
            DrawPolygon(g, ip, grid.Radius, 8, (float)-Math.PI / 8, pen, "八角形");
            ip.X = startIp.X;
            ip.Y += grid.StepY;
            pen.Color = Color.DarkRed;

            // 三角形。
            DrawPolygon(g, ip, grid.Radius, 3, (float)-Math.PI / 2, pen, "三角形");
            ip.X += grid.StepX;

            // 塗りつぶし五面体。
            var pts = DrawPolygon(g, ip, grid.Radius, 5, (float)-Math.PI / 2, pen, "塗りつぶし五面体");
            pts = new PointF[] { pts[0], pts[2], pts[4], pts[1], pts[3], };
            g.FillPolygon(pts, Color.FromArgb(fill, pen.Color));
            g.DrawPolygon(pts, pen);
            ip.X += grid.StepX;

            // ピラミッドを描くために単純な斜投影を設定します。
            var angle = Math.PI / 6;
            float s = (float)Math.Sin(angle);
            float c = (float)Math.Cos(angle);
            Func<float, float, float, PointF> project = (x_, y_, z_) => new PointF(x_ - c * y_ * 0.5f, -(z_ - s * y_ * 0.5f));
            Func<Vector3, PointF> p3d = v_ => project(v_.X, v_.Y, v_.Z);
            float hedge = grid.Radius; // 1/2 edge
            // デバッグ - 3つの軸を描画します。
            /*
            g.DrawLine(project(0, 0, 0), project(100, 0, 0), Color.Red);
            g.DrawLine(project(0, 0, 0), project(0, 100, 0), Color.Green);
            g.DrawLine(project(0, 0, 0), project(0, 0, 100), Color.Blue);
            */
            // 正方形のピラミッドを形成する 3D 点です。
            var pts3d = new Vector3[]
            {
                new Vector3(-hedge, -hedge, 0),
                new Vector3(hedge, -hedge, 0),
                new Vector3(hedge, hedge, 0),
                new Vector3(-hedge, hedge, 0),
                new Vector3(0, 0, hedge * 2),
            };
            // ピラミッドを描くために点を投影します。
            pts = pts3d.Select(v_ => p3d(v_)).ToArray();
            g.Transform = Matrix3x2.CreateTranslation(ip.X, ip.Y + hedge * 0.7f);
            // 表面のエッジ。
            g.DrawPolygon(new PointF[] { pts[4], pts[1], pts[2], pts[3], pts[4], pts[2] }, pen);
            // 影のエッジ。
            pen.Width /= 2;
            pen.Color = Color.FromArgb(fill, pen.Color);
            g.DrawLine(pts[0], pts[4], pen);
            g.DrawLine(pts[0], pts[1], pen);
            g.DrawLine(pts[0], pts[3], pen);
            g.FillPolygon(pts.Take(4).ToArray(), pen.Color);
            //
            g.Transform = Matrix3x2.Identity;
            DrawCaption(g, ip, grid.Radius, "ピラミッド");
            ip.X = startIp.X;
            ip.Y += grid.StepY;
            pen.Width *= 2;
            pen.Color = Color.Green;

            // 三角錐。
            float baseh = grid.Radius * 0.3f;
            pts = DrawPolygon(g, ip, grid.Radius, 3, (float)-Math.PI / 2, null, "三角錐");
            g.DrawLines(new PointF[] { pts[2], pts[0], pts[1] }, pen);
            rect = new RectangleF(pts[2].X, pts[2].Y - baseh / 2, pts[1].X - pts[2].X, baseh);
            g.FillEllipse(rect, Color.FromArgb(fill, pen.Color));
            g.DrawEllipse(rect, pen);
            ip.X += grid.StepX;

            // 平行四辺形(四角形を変形して利用します)。
            rect = new RectangleF(-grid.Radius * rectQx, -grid.Radius * rectQy, grid.Radius * 2 * rectQx, grid.Radius * 2 * rectQy);
            g.Transform = Matrix3x2.CreateSkew((float)Math.PI / 6, 0) * Matrix3x2.CreateTranslation(ip.X, ip.Y);
            g.DrawRectangle(rect, pen);
            g.Transform = Matrix3x2.Identity;
            DrawCaption(g, ip, grid.Radius, "平行四辺形");
            ip.X += grid.StepX;

            // 台形(DrawPolygon を使って四角形の点を取得します)。
            float dx = 10;
            pts = DrawPolygon(g, ip, grid.Radius, 4, (float)-Math.PI / 4, null, "台形");
            pts[0].X -= dx;
            pts[1].X += dx;
            pts[2].X -= dx;
            pts[3].X += dx;
            g.DrawPolygon(pts, pen);

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