C#によるPowerPoint図形操作のサンプル

# はじめに
プログラムからPowerPointの図形追加をする際、日本語の簡易なコードサンプルが見つけられず時間を奪われたので、誰かが同じ苦労をしないようにコードサンプルを載せます。使用言語はC#です。Visual Studio 2015を使います。

# PowerPointのファイル形式：OpenXML
C#からのOfficeのファイル操作には、[OpenXML SDK](https://msdn.microsoft.com/ja-jp/library/office/bb448854.aspx)を利用できます。

### OpenXML
Office 2007以降のPowerPointファイルは、[OpenXML](https://msdn.microsoft.com/ja-jp/library/office/gg278325.aspx)という仕様に準拠しています。OpenXMLの中でもPowerPointファイルを構成する言語を[PresentationML](https://msdn.microsoft.com/ja-jp/library/office/gg278318.aspx)と言います。実体はいくつかのXMLファイル（と、画像などのファイル）をzip圧縮したものです。試しにpptxファイルをzip解凍すると、アプリケーションが対象とするオブジェクト（例えばPowerPointのスライドマスター）に対応したXMLファイルを確認することができます。


### OpenXML SDK
OpenXML SDKは、OpenXML形式のファイルの読み書きを支援するライブラリです。

OpenXML SDKでは、XMLの各要素に対応したクラスがあり、要素の属性に対応したプロパティがあります。
例えばPowerPointでは、スライドを表すSlideクラス、図形を表すShapeクラス、図形の外線を表すOutlineがあり、線幅を表すOutlineのメンバ変数であるWidthがあります。

また、Microsoft の[ダウンロード センター](https://www.microsoft.com/en-us/download/details.aspx?id=30425)では、OpenXMLSDKToolV25.msiというツールがあります。
OpenXMLの構造をグラフィカルに表示できるだけでなく、XMLの不正な点を検出したり（Validate）、2 つの OpenXML ファイルを比較（Compare Files）したりできるようです。


# 準備：OpenXML SDKパッケージのインストール
準備として、[Nuget](https://www.nuget.org/)で[OpenXML SDKのパッケージ](https://github.com/OfficeDev/Open-XML-SDK)をインストールします。デフォルトのパッケージソースであるnuget.orgからは得られないので、まずパッケージソースの設定を行います。

### パッケージソースの設定
[OpenXML SDKのパッケージ](https://github.com/OfficeDev/Open-XML-SDK)に行って、Table 1: The latest builds are available via a MyGet feedからパッケージソースのFeed URLを得ます。Nuget V3であればhttps://dotnet.myget.org/F/open-xml-sdk/api/v3/index.json になります。

次の手順でパッケージソースをVisual Studio Nugetパッケージマネージャーの設定に追加します。

- Visual Studio のツール -> Nugetパッケージマネージャー -> パッケージマネージャー設定 をクリックしてオプションを開く
- Nugetパッケージマネージャー/パッケージソースの項目を開き、利用可能なパッケージソースに項目を追加
- 適当な名前（ここではdotnetopenxml）をつけ、ソースに先ほどのURLを記入して更新。


参考：[mygetからのパッケージ取得] (https://ufcpp.wordpress.com/2015/04/12/myget-%E3%81%8B%E3%82%89%E3%81%AE%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E5%8F%96%E5%BE%97/)、[github.com/OfficeDev/Open-XML-SDK] (https://github.com/OfficeDev/Open-XML-SDK)

### OpenXML SDKパッケージのインストール
ソリューションのNugetパッケージマネージャーを開いて、パッケージソースを先ほど加えたもの（dotnetopenxml）に変更すると、DocumentFormat.OpenXMLというパッケージが検出されます。それをインストールします。


# コードサンプル
ここからコードサンプルです。
すでにスライドが生成されたpptxファイル（hoge.pptx）が用意されているものとします。


## ShapeTreeオブジェクトの取得
図形を加えたり、望みの図形を得るためには、まずhoge.pptxの適当なスライドから、図形をまとめて管理するオブジェクトである[ShapeTree](https://msdn.microsoft.com/ja-jp/library/documentformat.openxml.presentation.shapetree(v=office.14).aspx)を取得します。この辺りのちゃんとした説明は[\[方法\] プレゼンテーション内の図形の塗りつぶしの色を変更する (Open XML SDK)]
(https://msdn.microsoft.com/ja-jp/library/office/cc850847.aspx)が詳しいです。

では、コードの全体像を示します。流れは次のようになります。

1. PresentationDocument.Openを使って、ファイル名fileNameからPresentationDocumentを取得
1. 最初のスライドのスライド番号（relId）を取得
1. relIdが示すスライドのShapeTreeを取得（ppshapeTree）
1. generateShapes()（後述）でppshapeTreeに図形を追加
1. 変更の保存をして終わり


```csharp
using DocumentFormat.OpenXml.Presentation;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml;
using D = DocumentFormat.OpenXml.Drawing;

class Program
{
	static void Main(string[] args)
	{
		ShapeTree ppshapeTree;
		string fileName = "hoge.pptx";
		PresentationDocument ppt = null;
		using (ppt = PresentationDocument.Open(fileName, true))
		{
			Console.WriteLine("\"" + fileName + "\" has opened.");
	
			// Get the relationship ID of the first slide.
			PresentationPart part = ppt.PresentationPart;
			OpenXmlElementList slideIds = part.Presentation.SlideIdList.ChildElements;
			string relId = (slideIds[0] as SlideId).RelationshipId;
		
			// Get the slide part from the relationship ID.
			SlidePart ppslide = (SlidePart)part.GetPartById(relId);
			if (ppslide != null)
			{
				ppshapeTree = ppslide.Slide.CommonSlideData.ShapeTree;
				generateShapes(ppshapeTree);
		
				ppslide.Slide.Save();
				Console.WriteLine("\"" + fileName + "\" has been changed.");
			}
		}	
	}
}
```

## 図形を生成してスライドに加えるメソッドの作成
generateShapes()の中で使用する汎用的な図形生成メソッドを作成していきます。
名前はAddShape_()とします。

オブジェクトの追加は、そのオブジェクトの親のAppendメソッドによって追加していきます。図形の追加であれば、Shapeインスタンスを生成して、ppshapeTreeに対してAppendすればいいということになります。


### Shapeインスタンスの生成

Shapeインスタンスを追加したい図形オブジェクトになるようにするには、そのメンバ変数で指定すればいいというわけではなく、特徴を表すインスタンスをAppendしていきます。

Shapeインスタンスには、次の4つのクラスのインスタンスをAppendする必要があります。

- NonVisualShapeProperties
- ShapeProperties
- ShapeStyle
- TextBody

したがって、AddShape_メソッドは次の手順で書かれています。

1. Shapeインスタンスshape1を生成
1. NonVisualShapePropertiesを生成してshape1にAppend
1. ShapePropertiesに引数を渡して生成してshape1にAppend
1. ShapeStyleを生成してshape1にAppend
1. TextBodyを生成してshape1にAppend
1. shape1を引数ShapeTree ppshapeTreeにAppend

ShapeインスタンスにAppendするインスタンスはmakeProperties（後述）をはじめとしたメソッドの中で作成しています。
これらのインスタンスにもまた、その子オブジェクトのインスタンスをAppendしていく必要があります。
足らないとファイルが開けなかったり、PowerPointを開いたときに正しく図形が表示できなかったりします。

Shapeインスタンスを追加したい図形オブジェクトになるようにするには、ShapePropertiesインスタンス以下を指定していきます。だから、AddShape_メソッドのppshapeTree以外の引数はmakeShapeProperties()に渡しています。makeShapeProperties()だけが関心事なので、以降でmakeShapeProperties()とそれ以外は分けて表示します。


```csharp
    static class AddShape
    {
        private const int cm2shapescale = 360000;
        private const int degree2shapescale = 60000;

        static void AddShape_(
                ShapeTree ppshapeTree,
                D.ShapeTypeValues shapeType = D.ShapeTypeValues.Rectangle,// Any of the built-in shapes (ellipse, rectangle, etc)
                string fill_rgbColorHex = "EEECE1", // Hexadecimal RGB color code to fill the shape.
                bool isnooutline = false,
                D.SchemeColorValues outlineSchemeColor = D.SchemeColorValues.Text1,
                float x = 1, // Represents the shape x position in 1 cm.
                float y = 2, // Represents the shape y position in 2 cm.
                float width = 2, // Shapw width in in 2 cm.
                float height = 2, // Shapw height in in 2 cm.
                bool horizontalFlip = false,
                bool verticalFlip = false,
                int angle = 0,   //degree
                bool isdashline = false,
                bool isTailEndArrow = false,
                bool isHeadEndArrow = false
            )
        {
            Shape shape1 = new Shape();
            shape1.Append(makeNonVisualShapeProperties());
            shape1.Append(makeShapeProperties(
                shapeType: shapeType,
                fill_rgbColorHex: fill_rgbColorHex,
                isnooutline: isnooutline,
                outlineSchemeColor: outlineSchemeColor,
                x: getcm2shapescale(x),
                y: getcm2shapescale(y),
                width: getcm2shapescale(width),
                height: getcm2shapescale(height),
                horizontalFlip: horizontalFlip, verticalFlip: verticalFlip,
                angle: getDegree2shapescale(angle),
                isdashline: isdashline,
                isTailEndArrow: isTailEndArrow,
                isHeadEndArrow: isHeadEndArrow)
                );
            shape1.Append(makeShapeStyle());
            shape1.Append(makeTextBody());
            ppshapeTree.Append(shape1);

        }

        static long getcm2shapescale(float cm_val)
        {
            return (long)(cm_val * cm2shapescale);
        }

        static int getDegree2shapescale(int angle)
        {
            return angle * degree2shapescale;
        }

        .
        .
        .

    }
```


AddShape_メソッドの引数は次のようなことを表現します。
<dl>
<dt>
shapeType
<dt>
<dd>
図の外形になります。線（Line）にしたり四角形（Rectangle）にしたりできます。[ShapeTypeValues](https://msdn.microsoft.com/ja-jp/library/documentformat.openxml.drawing.shapetypevalues(v=office.14).aspx)に指定できる図の種類が示されています。
<dd>
<dt>
fill_rgbColorHex
<dt>
<dd>
図形の塗りつぶしの色になります。16進数RGBカラーコードを入力します。
<dd>
<dt>
outlineSchemeColor
<dt>
<dd>
図形の外線の線種・線色になります。[SchemeColorValues](https://msdn.microsoft.com/ja-jp/library/documentformat.openxml.drawing.schemecolorvalues(v=office.14).aspx)に指定できる値が示されています。
<dd>
<dt>
xとy
<dt>
<dd>
それぞれ横方向、縦方向の座標を表します。PowerPointスライドの左上が(x=0, y=0)です。cm単位です。
<dd>
</dl>


あとはだいたいわかると思います。getcm2shapescale()やgetDegree2shapescale()でShapeProperties以下での座標や角度のスケールに変換してからmakeShapePropertiesに値を渡します。



### ShapePropertiesインスタンスの生成
makeShapeProperties()では、引数を該当のインスタンスのメンバ変数に代入していきます。

```charp
static private ShapeProperties makeShapeProperties(
            D.ShapeTypeValues shapeType = D.ShapeTypeValues.Rectangle,// Any of the built-in shapes (ellipse, rectangle, etc)
            string fill_rgbColorHex = "EEECE1", // Hexadecimal RGB color code to fill the shape.
            bool isnooutline = false,  //no outline
            D.SchemeColorValues outlineSchemeColor = D.SchemeColorValues.Text1,
            long x = 360000, // Represents the shape x position in 1/36000 cm.
            long y = 720000, // Represents the shape y position in 1/36000 cm.
            long width = 720000, // Shapw width in in 1/36000 cm.
            long height = 720000, // Shapw height in in 1/36000 cm.
            bool horizontalFlip = false,
            bool verticalFlip = false,
            int angle = 0,  //2700000 * 4 = 180 degree
            bool isdashline = false,
            bool isTailEndArrow = false,
            bool isHeadEndArrow = false
            )
        {
            ShapeProperties shapeProperties1 = new ShapeProperties();

            D.Transform2D transform2D1 = new D.Transform2D();
            D.Offset offset1 = new D.Offset() { X = x, Y = y };
            D.Extents extents1 = new D.Extents() { Cx = width, Cy = height };

            transform2D1.HorizontalFlip = horizontalFlip;
            transform2D1.VerticalFlip = verticalFlip;
            transform2D1.Rotation = angle;

            transform2D1.Append(offset1);
            transform2D1.Append(extents1);

            D.PresetGeometry presetGeometry1 = new D.PresetGeometry() { Preset = shapeType };
            D.AdjustValueList adjustValueList1 = new D.AdjustValueList();

            presetGeometry1.Append(adjustValueList1);

            D.SolidFill solidFill1 = new D.SolidFill();
            D.RgbColorModelHex rgbColorModelHex1 = new D.RgbColorModelHex() { Val = fill_rgbColorHex };

            solidFill1.Append(rgbColorModelHex1);

            D.Outline outline1 = new D.Outline() { Width = 12700 };
            if (isnooutline)
            {
                D.NoFill nofill = new D.NoFill();
                outline1.Append(nofill);
            }
            else
            {
                D.SolidFill solidFill2 = new D.SolidFill();
                D.SchemeColor schemeColor1 = new D.SchemeColor() { Val = outlineSchemeColor };
                solidFill2.Append(schemeColor1);
                outline1.Append(solidFill2);

                //dash
                if (isdashline)
                {
                    D.PresetDash presetDash = new D.PresetDash() { Val = D.PresetLineDashValues.Dash };
                    outline1.Append(presetDash);
                }

                //arrow
                if (isTailEndArrow)
                {
                    D.TailEnd tailend = new D.TailEnd() { Type = D.LineEndValues.Arrow };
                    outline1.Append(tailend);
                }
                if (isHeadEndArrow)
                {
                    D.HeadEnd headend = new D.HeadEnd() { Type = D.LineEndValues.Arrow };
                    outline1.Append(headend);
                }
            }

            shapeProperties1.Append(transform2D1);
            shapeProperties1.Append(presetGeometry1);
            shapeProperties1.Append(solidFill1);
            shapeProperties1.Append(outline1);
            return shapeProperties1;
        }

```


### その他のインスタンスの生成
おまじないを書きます。[ここ](http://stackoverflow.com/questions/18810001/how-to-insert-a-shape-in-a-powerpoint-slide)を参考にしました。

```charp
        static private ShapeStyle makeShapeStyle()
        {
            ShapeStyle shapeStyle1 = new ShapeStyle();

            D.LineReference lineReference1 = new D.LineReference() { Index = (UInt32Value)2U };

            D.SchemeColor schemeColor2 = new D.SchemeColor() { Val = D.SchemeColorValues.Accent1 };
            D.Shade shade1 = new D.Shade() { Val = 50000 };

            schemeColor2.Append(shade1);

            lineReference1.Append(schemeColor2);

            D.FillReference fillReference1 = new D.FillReference() { Index = (UInt32Value)1U };
            D.SchemeColor schemeColor3 = new D.SchemeColor() { Val = D.SchemeColorValues.Accent1 };

            fillReference1.Append(schemeColor3);

            D.EffectReference effectReference1 = new D.EffectReference() { Index = (UInt32Value)0U };
            D.SchemeColor schemeColor4 = new D.SchemeColor() { Val = D.SchemeColorValues.Accent1 };

            effectReference1.Append(schemeColor4);

            D.FontReference fontReference1 = new D.FontReference() { Index = D.FontCollectionIndexValues.Minor };
            D.SchemeColor schemeColor5 = new D.SchemeColor() { Val = D.SchemeColorValues.Light1 };

            fontReference1.Append(schemeColor5);

            shapeStyle1.Append(lineReference1);
            shapeStyle1.Append(fillReference1);
            shapeStyle1.Append(effectReference1);
            shapeStyle1.Append(fontReference1);
            return shapeStyle1;

        }



        static private TextBody makeTextBody()
        {
            TextBody textBody1 = new TextBody();
            D.BodyProperties bodyProperties1 = new D.BodyProperties() { RightToLeftColumns = false, Anchor = D.TextAnchoringTypeValues.Center };
            D.ListStyle listStyle1 = new D.ListStyle();

            D.Paragraph paragraph1 = new D.Paragraph();
            D.ParagraphProperties paragraphProperties1 = new D.ParagraphProperties() { Alignment = D.TextAlignmentTypeValues.Center };
            D.EndParagraphRunProperties endParagraphRunProperties1 = new D.EndParagraphRunProperties() { Language = "es-ES" };

            paragraph1.Append(paragraphProperties1);
            paragraph1.Append(endParagraphRunProperties1);

            textBody1.Append(bodyProperties1);
            textBody1.Append(listStyle1);
            textBody1.Append(paragraph1);
            return textBody1;
        }


        static private NonVisualShapeProperties makeNonVisualShapeProperties()
        {
            NonVisualShapeProperties nonVisualShapeProperties1 = new NonVisualShapeProperties();
            NonVisualDrawingProperties nonVisualDrawingProperties1 = new NonVisualDrawingProperties() { Id = (UInt32Value)4U, Name = "1 Shape Name" };
            NonVisualShapeDrawingProperties nonVisualShapeDrawingProperties1 = new NonVisualShapeDrawingProperties();
            ApplicationNonVisualDrawingProperties applicationNonVisualDrawingProperties1 = new ApplicationNonVisualDrawingProperties();

            nonVisualShapeProperties1.Append(nonVisualDrawingProperties1);
            nonVisualShapeProperties1.Append(nonVisualShapeDrawingProperties1);
            nonVisualShapeProperties1.Append(applicationNonVisualDrawingProperties1);
            return nonVisualShapeProperties1;
        }

```


## 図形の生成例
generateShapes()の中で以降を呼び出せば、該当の図形が生成できます。

### 線
(x1,y1)から(x2,y2)への波線矢印

```csharp

AddShape_(
	ppShapeTree,
	D.ShapeTypeValues.Line,
	outlineSchemeColor: D.SchemeColorValues.Text1,
	x: (x1),
	y: (y1),
	width: (x2 - x1),
	height: (y2 - y1),
	isdashline: true,
	isTailEndArrow: true,
	isHeadEndArrow: false
	);
```




### 四角形


```csharp
AddShape_(
	ppShapeTree,
	D.ShapeTypeValues.Rectangle, 
	x: (x),
	y: (y),
	width: (w),
	height: (h)
	);
```


### 回転、反転する
AddShape_のhorizontalFlipにtrueを渡せば水平方向に反転。
angleに角度を渡せば回転。