AppleScriptでPowerPointを操作する Part1｜図形の操作


## 環境

* macOS 11.4 (Big Sur)
* PowerPoint for Mac 16.49（Office 365、2021）

## 目指すところ

以下のような操作を、ワンクリックやワンボタンで行えるようにする

* PowerPointのオブジェクト（書式つき）を一発で生成したい
  * 図形によってデフォルト書式を変えたい（PowerPoint本来の機能では全てのShapeで同じ書式しかデフォルトにできない）
* 段落（行間、段落間距離）の設定を一発で行いたい
* PowerPointの表の編集（行の追加、削除）を、右クリックを使わず行いたい[^1]
  * 右クリックを押した瞬間、選択範囲が変わるのが面倒
* 複数のオブジェクトをグリッド上に整列させたい

[^1]: 表の編集については、この記事が長くなったため、分割先 (https://qiita.com/yuoya/items/b1f10789acf73420ea80) に記載しています


### 活用方法
左手デバイス(StreamDeck等)で、ワンクリックでできることを目的に作成しました。
デバイスを持っていない人でも、メニューに割り当てたり、Automatorと組み合わせることでショートカットを設定することができます。

![整列.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115422/1adb3d5c-4241-22c7-83e3-018fd69dc83d.gif)


## なぜAppleScriptか

AppleScriptの処理で特徴的なのは、自分が操作したり選択中の書類、書類中のオブジェクトに対して処理できることです。
それはマクロで良いのではないか？と思われるかもしれませんが、PowerPointでは一つ一つのファイルにマクロを埋め込む必要があり、現実的ではありません。
昨今ではpythonの適用範囲が広がっており、PowerPointを操作することも可能ですが、まず環境構築が大変であること、選択中のプレゼンファイルやページを取得できない、という問題があります。
１番の問題はWindowsでは使えないことでしょうか。作業がmac中心の方であれば有力な選択肢だと思います。

|                                  | AppleScript |   マクロ   | Python |
| -------------------------------- | :---------: | :--------: | :----: |
| 選択中のオブジェクトを処理できる |      ○      |     ○      |   ×    |
| ファイルに埋め込みが必要ない     |      ○      |     ×      |   ○    |
| 動作が早い                       |      ×      |     ○      |   △    |
| メニュー/ショートカット等割り当てられる   |      ○      |     ○      |   △    |
| アプリ間連携ができる             |      ○      | officeのみ |   △    |
| 環境構築が不要             |      ○      | ○ |    ×   |
| Windowsでも使える             |      ×      | ○ |    ○    |

## AppleScriptで気をつけるところ

英語に近く、読みやすくて習得しやすいと言われているAppleScriptですが、C系やJava, Python等のプログラムを見慣れていた自分にはいくつか混乱した部分がありました。

#### `tell ... end tell`の使い方
AppleScriptの例では、最初にtell文を用いてアプリケーションを起動するものが多いため、
それに慣れて、「tell文=Applicationを指定する文」だと思っていたら他の人のプログラムが理解できず、ハマりました。

tell文はオブジェクトの送信範囲を指定する役割があります。
VBAでいうところのWith文みたいなもの、というのが今の自分の理解です。
特にオブジェクトの階層が深くなるPowerPointでは、tell文をうまく使わないと指定するだけで面倒です。
また、tell文の中では自分で定義したハンドラを呼び出すときにmy もしくは of meが必要になります。

#### 値の代入
他の言語では
`変数 = 代入する値`となるところが
`set 変数 to 代入する値`と表記されます。

別段難しさがあるわけではないのですが、記号をなるべく使わずに表記するAppleScriptは、命令語をきちんと覚えていない状態で見ると、変数なのか命令語なのかの区別が視覚的にしづらいです。ネット上のコードでもカラーリングされていないことも多く、余計に混乱しますね。

文章として自然に作ろうとしているぶん、初見だと、どれが変数でどれがコマンドなのかがわかりにくく、面食らいました。

#### 階層オブジェクトの指定
他の言語では
`AAA.BBB.CCC`となるところが
`CCC of BBB of AAA`と表記されます。

確かに英語に基づくとそうなのですが、普通の言語とは順序も逆ですし、
階層が深くなると見づらくなってきます。

この見辛さを解消するために、tell文を多用します。

## AppleScriptのアプリケーション対応

アプリケーションをコントロールするコマンド等は、どこで調べれば良いでしょうか。
これは「スクリプトエディタ」の基本機能に実装されていますので、そこから調べることが確実です。
スクリプトエディタのメニュー「ファイル」>「用語説明を開く」を選択すると、対応しているアプリケーション一覧が現れます。

## PowerPointのページ、オブジェクトの階層構造

PowerPointを操作する上では、Applescriptの知識だけでなく、PowerPointのオブジェクト構造を理解することが重要です。（マクロで組む場合と同様です）

* 参考リンク｜PowerPointのオブジェクトについて

https://tonari-it.com/powerpoint-vba-slides-collection-slide-object/

もう一つ、applescriptの選択範囲を取得するselectionはVBAのマクロとは指定範囲や使い方が異なるところも注意です。

selectionの挙動は私もまだ把握しきれておらず、オブジェクトの種類によって試行錯誤しながら確認せざるを得ないです。以下の挙動は全て違います。

* 今のページ（番号）の取得｜returnは数値
  * `slide index of slide range of selection of active window`
* 選んでいるオブジェクトの取得｜returnはshape range（複数オブジェクトの場合もあり）
  * `shape range of selection of active window`
* 表の選んでいる部分の取得｜returnは真偽値
  * `selected of cell xx of row xx of table object of shape table xx of shape range of selection of active window`


## コードの実装
それでは実際にコードを実装していきます。
AppleScript初心者なので、もっと良い書き方や実装がある場合はぜひ教えていただけると助かります。

### 図形（吹き出し）の生成

#### まずはPowerPointを起動（もしくは起動中のPowerPointをアクティブに）

**スクリプトエディタ**を立ち上げ、以下のコードを入力して実行してみましょう

```applescript
tell application "Microsoft PowerPoint"
	activate
end tell
```

PowerPointが起動したでしょうか。
それではもう少し使えそうなコードを書いていきます。

#### 適当な場所（最初のページ）に図形オブジェクト(Shape)を配置

次に図形を生成するコードです。
下記を実行し、最初のページに角丸四角形が生成されれば成功です。

```applescript
tell application "Microsoft PowerPoint"

	-- 図形の位置とサイズ、形状を設定
	set x_position to 30
	set y_position to 30
	set dia_width to 100
	set dia_height to 100
	set shape_type to autoshape rounded rectangle

	--最初のページ内で操作
	tell slide 1 of active presentation
		-- 図形を生成し、TheShapeと命名
		set TheShape to make new shape at the end with properties {auto shape type:shape_type, left position:x_position, top:y_position, width:dia_width, height:dia_height}
	end tell
end tell
```

#### 今選んでいるページに図形を配置
これを元に少しずつ改造していきましょう。先程のコードでは常に１ページ目に図形が生成されてしまいましたが、選択しているページに図形を生成するように変えましょう。

上記コードの、最初のページ内で操作という部分を以下のように変えます。

```applescript
--現在のスライド番号を取得
set n to slide index of slide range of selection of active window 

--現在のスライド内で操作
tell slide n of active presentation 
	-- ここで図形生成（省略）
end tell

```

#### 図形を吹き出しにしてみる
次に図形を角丸四角形から吹き出しに変更していきます。

図形の名称の調べ方ですが、スクリプトエディタの「用語説明」（メニュー：ファイル>用語説明を開く）から確認できます。
PowerPointの用語説明を選んだら、**auto shape type**を検索してみましょう。ずらずらと図形の種類の名前が出てきたかと思います。この順番はPowerPointのリボンメニューと同じ構成になっていますので、そこから頑張って探してください。
吹き出しは**autoshape rectangular callout**です。

```applescript
	set shape_type to autoshape rectangular callout
```

#### Shapeの書式を色々設定

最後に書式を設定していきます。

**用語説明**の中で**Shape**のプロパティを調べると色々出てきます。**xx format**となっているところを変えていきます。色の設定はRGBで指定する方法とテーマ色から選ぶ方法のどちらもあります。

```applescript
tell TheShape

  set line weight of line format of it to 1 --枠線の太さを1に
  set line style of line format of it to single line --枠線を実線に
  set dash style of line format of it to line dash style solid --枠線を実線に
  set fore color of line format of it to {200, 200, 200} --枠線の色をグレーに
  --set fore color theme index of fill format of it to sixth accent theme color --塗りつぶしの色をアクセント6に
  set fore color of fill format of it to {229, 229, 229} --塗りつぶしの色をグレーに
  set font color theme index of font of text range of text frame of it to second text theme color --フォントをテキスト2に
  set font size of font of text range of text frame of it to 14 --フォントサイズを14に

end tell
```



#### 全て合わせる（選択中のページに、吹き出しを生成し、書式を設定）
ここまでの内容を全て含んだコードは以下になります。

```applescript
tell application "Microsoft PowerPoint"
	
	-- 図形の位置とサイズ、形状を設定
	set x_position to 15
	set y_position to 15
	set dia_width to 200
	set dia_height to 100
	set shape_type to autoshape rectangular callout
	-- 
	set n to slide index of slide range of selection of active window --現在のスライド番号を取得
	tell slide n of active presentation --現在のスライド内で操作
		--図形を生成し、TheShapeと命名
		set TheShape to make new shape at the end with properties ¬
			{left position:x_position, top:y_position, width:dia_width, height:dia_height, auto shape type:shape_type}
		tell TheShape
			
			set line weight of line format of it to 1 --枠線の太さを1に
			set line style of line format of it to single line --枠線を実線に
			set dash style of line format of it to line dash style solid --枠線を実線に
			set fore color of line format of it to {200, 200, 200} --枠線の色をグレーに
			--set fore color theme index of fill format of it to sixth accent theme color --塗りつぶしの色をアクセント6に
			set fore color of fill format of it to {229, 229, 229} --塗りつぶしの色をグレーに
			set font color theme index of font of text range of text frame of it to second text theme color --フォントをテキスト2に
			set font size of font of text range of text frame of it to 14 --フォントサイズを14に
			
		end tell
		
	end tell
	
end tell

```

### 選択した全ての図形を一括変換

次に選択した図形の形状や書式を一気に変換するやり方についてみていきます。

#### 選択した図形を取得

選択範囲を表すオブジェクトは`shape range`です。
現在選択している図形（複数）を取得するコードは以下になります。
`shape range of selection of active window`
この中から一つ一つ図形を抜き出して、変換していくことになります。
`shape X of shape range`
のようにXの数値を変えていくことで一つ一つの図形を抜き出せます。

#### 吹き出しへ一括変換

それでは実際にコードを見ていきましょう。
いくつかの図形を選択し、以下のコードを実行してみてください。

```applescript
tell application "Microsoft PowerPoint"
	
	set theShapeRange to shape range of selection of active window --現在の選択範囲をtheShapeRangeと設定
	set shapeCount to (count shapes of theShapeRange) --いくつ選択しているかを取得
	
	-- 選んでいる全てのShapeを吹き出しに変更
	repeat with i from 1 to shapeCount
		set auto shape type of shape i of theShapeRange to autoshape rectangular callout --図形を吹き出しに			
	end repeat
end tell
```

形状が変化したでしょうか？

#### 書式を含めて一括変換

最後に書式も含めて一括変換していきたいと思います。
設定方法は生成の時と全く同じです。

```applescript
tell application "Microsoft PowerPoint"
	
	-- 選んでいる全てのShapeを吹き出しに変更
	-- 
	set theShapeRange to shape range of selection of active window
	set shapeCount to (count shapes of theShapeRange)
	repeat with i from 1 to shapeCount
		tell shape i of theShapeRange
			set auto shape type of it to autoshape rectangular callout
			--図形を吹き出しに
			set line weight of line format of it to 1 --枠線の太さを1に
			set line style of line format of it to single line --枠線を実線に
			set dash style of line format of it to line dash style solid --枠線を実線に
			set fore color of line format of it to {200, 200, 200} --枠線の色をグレーに
			--set fore color theme index of fill format of it to sixth accent theme color --塗りつぶしの色をアクセント6に
			set fore color of fill format of it to {229, 229, 229} --塗りつぶしの色をグレーに
			set font color theme index of font of text range of text frame of it to second text theme color --フォントをテキスト2に
			
		end tell
	end repeat
	
end tell
```

### 選択した図形の段落設定（行間、段落間設定）を一括変換

デザインを揃える上では、行間や段落間設定を揃えておくことが重要です。
段落設定はかなり深い部分にあります。
`paragraph format of text range of text frame of shape X`

AppleScriptとして新たに覚えることはありませんが、人にとっては有用なScriptだと思いますので載せておきます。

```applescript
tell application "Microsoft PowerPoint"
	
	-- 選んでいる全てのShapeの段落を設定
	set theShapeRange to shape range of selection of active window
	set shapeCount to (count shapes of theShapeRange)
	
	repeat with i from 1 to shapeCount
		tell paragraph format of text range of text frame of shape i of theShapeRange
			set line rule within of it to true --行間の設定を行の倍数に
			set space within of it to 1.1 --行間を1.1倍に
			set line rule before of it to false --段落前の設定をポイントに
			set space before of it to 3 --段落前を3ポイントに
		end tell
	end repeat
	
end tell
```

### 選択した図形をグリッド上に並べる
先程の内容を応用して、GIF画面にあったような図形を並べるスクリプトを実装してみます。
グリッドの幅や列数を自動で算出することも可能ではあるのですが、図形の種類やサイズが様々で、例外処理などを入れると複雑になってしまうため、ここでは最初にダイアログを出して、列数と間隔のサイズを直接入力できるようにします。  
ただ、AppleScriptでは複数の値を入力するダイアログがないので、カンマ区切りで入力を行い、それぞれ分割して取得することにします。

```AppleScript
tell application "Microsoft PowerPoint"
	start log
	-- ●選んでいる全てのShapeをGrid上に配列
	
	--初期値を設定
	set startX to 100
	set startY to 100
	
	--複数の値をカンマ区切りで入力
	display dialog "並べる列数（１行に入れる個数）と間隔（横、縦）を数字で入力" default answer "5,100,150"
	set ans to text returned of result
	
	--文字列の分割
	set org_delimi to text item delimiters of AppleScript -- オリジナルの区切り文字をとっておく
	set text item delimiters of AppleScript to "," -- ","で区切る
	set ans1 to text item 1 of ans -- １つめの値を変数ans1 に格納
	set ans2 to text item 2 of ans
	set ans3 to text item 3 of ans
	set text item delimiters of AppleScript to org_delimi -- 操作終了後にオリジナルの区切り文字に戻す
	
	--入力した値を設定
	set n_col to ans1
	set spaceX to ans2
	set spaceY to ans3
	
	
	tell active window
		
		set theShapeRange to shape range of selection of it
		set shapeCount to (count shapes of theShapeRange) --選択している図形の数を取得
		
		
		--全ての図形に設定
		repeat with i from 0 to shapeCount - 1
			tell shape (i + 1) of theShapeRange
				--列数の除算と剰余で行数と列数を算出
				set i_row to (i div n_col)
				set i_col to (i mod n_col)
				--位置を設定
				set left position of it to startX + spaceX * i_col
				set top of it to startY + spaceY * i_row
				log "---"
				log "i:" & i
				log "行" & i_row
				log "列" & i_col
			end tell
		end repeat
	end tell
	
end tell


```
## メニューへ設定

ここで作成したAppleScriptの活用方法ですが、最もわかりやすいのはメニューへの設定ではないでしょうか。

現在アクティブのアプリケーションに合わせてメニューを変更する機能がOSレベルで実装されています。

設定方法も非常に簡単で、メニューの設定をした後、該当のフォルダにスクリプトファイルを配置するだけです。

以下、その方法を記載します。

#### まずはメニューを表示する

スクリプトエディタの「環境設定」>「一般」の中に「メニューバーにスクリプトメニューを表示」という項目がありますので、ここにチェックをつけてください。

これでメニューバーにスクリプトのアイコンが表示されるようになります。

#### アプリケーションスクリプトを設定する

該当のアプリケーションを起動します。（今回はPowerPoint）

その状態でスクリプトメニューを表示すると「スクリプトフォルダを開く」の下に「PowerPointスクリプトフォルダを開く」というメニューが出てきます。

これをクリックし、開いたフォルダにスクリプトをコピーしておけば、いつでもメニューから呼び出せるようになります。


## 関連記事
https://qiita.com/yuoya/items/b1f10789acf73420ea80


## 参考リンク
日本語の情報が非常に少なく、海外でもOfficeに関連したAppleScriptの解説は非常に少ないです。
その中で、以下のページには多くのヒントをいただきました。

##### AppleScript全般（入門から応用まで）
http://tonbi.jp/AppleScript/

##### AppleScript言語詳細
https://seraphy.hatenablog.com/entries/2010/04/08
https://seraphy.hatenablog.com/entries/2010/04/11

##### PowerPointのさまざまな選択状態について（VBA情報）
https://tonari-it.com/powerpoint-vba-selection-type/

##### AppleScript詳細＆最新情報
http://piyocast.com/as/
AppleScriptでここまでできるのか、という応用事例多数。
内容はかなり高度です。