Created
December 17, 2017 18:29
-
-
Save PSingletary/92c7876902bf702782739424a6f60a9c to your computer and use it in GitHub Desktop.
PowerPoint Powered by PowerShell (Materials for http://www.slideshare.net/miriyagi/powerpoint-powered-by-powershell)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Interop.PowerPoint") | |
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") | |
[string] $Source = "$(Split-Path $Script:MyInvocation.MyCommand.Path -Parent)\Slide.html" | |
[int] $DefaultTitleHeight = 150 | |
function Add-Textbox { | |
param ( | |
[object] $Slide, | |
[string] $Text, | |
[int[]] $Margin = @(20, 20, 20, 170), | |
[int[]] $Padding = @(20, 20, 20, 20), | |
[int] $ForeColor = 0x000000, | |
[int] $BackColor = 0xFFFFFF, | |
[string] $AsciiFont = "Segoe UI", | |
[string] $FarEastFont = "メイリオ", | |
[int] $FontSize = 36, | |
[Microsoft.Office.Core.MsoVerticalAnchor] $VerticalAlignment = [Microsoft.Office.Core.MsoVerticalAnchor]::msoAnchorTop, | |
[Microsoft.Office.Interop.PowerPoint.PpParagraphAlignment] $HorizontalAlignment = [Microsoft.Office.Interop.PowerPoint.PpParagraphAlignment]::ppAlignLeft, | |
[int] $ThemeColor = -1, | |
[bool] $Bullet = $false | |
) | |
if ($Slide -eq $null -or $Text -eq $null -or $Padding.Count -ne 4 -or $Margin.Count -ne 4) { | |
throw "Invalid Arguments $Slide, $Text, $Padding, $Margin" | |
} | |
[float] $pWidth = $Slide.Master.Width - $Margin[0] - $Margin[2] | |
[float] $pHeight = $Slide.Master.Height - $Margin[1] - $Margin[3] | |
[Microsoft.Office.Core.MsoTextOrientation] $orientation = [Microsoft.Office.Core.MsoTextOrientation]::msoTextOrientationHorizontal | |
[object] $textbox = $Slide.Shapes.AddTextbox($orientation, $Margin[0], $Margin[1], $pWidth, $pHeight) | |
if ($textbox -eq $null) { | |
throw "Could not create a text box" | |
} | |
[object] $frame = $textbox.TextFrame | |
$frame.AutoSize = [Microsoft.Office.Interop.PowerPoint.PpAutoSize]::ppAutoSizeNone | |
$frame.MarginTop = $Padding[1] | |
$frame.MarginLeft = $Padding[0] | |
$frame.MarginRight = $Padding[2] | |
$frame.MarginBottom = $Padding[3] | |
$frame.VerticalAnchor = $VerticalAlignment | |
[object] $range = $frame.TextRange | |
$range.Text = $Text | |
[object] $format = $range.ParagraphFormat | |
$format.Alignment = $HorizontalAlignment | |
if ($Bullet) { | |
$format.Bullet.Visible = 1 | |
$format.Bullet.Type = 1 | |
$format.SpaceWithin = 2 | |
} | |
[object] $font = $range.Font | |
$font.NameFarEast = $FarEastFont | |
$font.NameAscii = $AsciiFont | |
$font.Size = $FontSize | |
$font.Color.RGB = $ForeColor | |
if ($ThemeColor -eq -1) { | |
$textbox.Fill.BackColor.RGB = $BackColor | |
} else { | |
$textbox.Fill.BackColor.ObjectThemeColor = $ThemeColor | |
} | |
$textbox.Height = $pHeight | |
return $textbox | |
} | |
function Add-TitleSlide { | |
param ( | |
[object] $Presentation, | |
[string] $Title, | |
[string] $Author | |
) | |
[object] $slide = $Presentation.Slides.Add($Presentation.Slides.Count + 1, 12) | |
[hashtable] $titleArgs = @{ | |
Slide = $slide; | |
Text = $Title; | |
Margin = @(0, 0, 0, 150); | |
Padding = @(50, 50, 50, 20); | |
BackColor = 0x2647D2; | |
ForeColor = 0xFFFFFF; | |
FontSize = 72; | |
VerticalAlignment = [Microsoft.Office.Core.MsoVerticalAnchor]::msoAnchorBottom; | |
} | |
[hashtable] $authorArgs = @{ | |
Slide = $slide; | |
Text = $Author; | |
Margin = @(0, 390, 0, 0); | |
Padding = @(50, 20, 50, 50); | |
ForeColor = 0x2647D2; | |
FontSize = 36; | |
VerticalAlignment = [Microsoft.Office.Core.MsoVerticalAnchor]::msoAnchorTop; | |
} | |
[object] $titleTextbox = Add-Textbox @titleArgs | |
[object] $authorTextbox = Add-Textbox @authorArgs | |
return $slide | |
} | |
function Add-SlideTitle { | |
param ( | |
[object] $Slide, | |
[string] $Title = [string]::Empty | |
) | |
if ($Slide -eq $null) { | |
return $null | |
} | |
[int[]] $colors = @(3, 5, 6, 7, 8, 9, 10) | |
[hashtable] $args = @{ | |
Slide = $Slide; | |
Text = $Title; | |
Margin = @(0, 390, 0, 0); | |
HorizontalAlignment = [Microsoft.Office.Interop.PowerPoint.PpParagraphAlignment]::ppAlignCenter; | |
VerticalAlignment = [Microsoft.Office.Core.MsoVerticalAnchor]::msoAnchorMiddle; | |
FontSize = 48; | |
ForeColor = 0xFFFFFF; | |
ThemeColor = $colors[$Slide.Parent.Slides.Count % $colors.Count]; | |
} | |
[object] $textbox = Add-Textbox @args | |
return $textbox | |
} | |
function Add-Image { | |
param ( | |
[object] $Slide, | |
[string] $Image, | |
[int] $TitleHeight = $DefaultTitleHeight, | |
[string] $Align = "middle", | |
[string] $Cite = [string]::Empty, | |
[switch] $ScaleToFit | |
) | |
if ($Slide -eq $null -or [string]::IsNullOrEmpty($Image)) { | |
return $null | |
} | |
if ($Image -match "^https?://") { | |
# Download a remote file | |
[Uri] $uri = New-Object Uri $Image | |
[Net.WebClient] $client = New-Object Net.WebClient | |
$Image = Join-Path -Path $env:TEMP -ChildPath $uri.Segments[$uri.Segments.Length - 1] | |
$client.DownloadFile($uri, $Image) | |
} elseif (![IO.Path]::IsPathRooted($Image)) { | |
# Convert a relative path to an absolute one | |
[string] $dirName = [IO.Path]::GetDirectoryName($Source) | |
$Image = Join-Path -Path $dirName -ChildPath $Image | |
} | |
# Calculate the width & height | |
[float] $slideHeight = $Slide.Master.Height - $TitleHeight | |
[float] $slideWidth = $Slide.Master.Width | |
[object] $bitmap = New-Object Drawing.Bitmap $Image | |
[float] $magX = $slideWidth / $bitmap.Width | |
[float] $magY = $slideHeight / $bitmap.Height | |
[float] $mag = if ($Fit.IsPresent -or $Align -eq "fit") { [Math]::Min($magX, $magY) } else { [Math]::Max($magX, $magY) } | |
[float] $pictureWidth = $bitmap.Width * $mag | |
[float] $pictureHeight = $bitmap.Height * $mag | |
[object] $picture = $Slide.Shapes.AddPicture($Image, $false, $true, 0, 0, $pictureWidth, $pictureHeight) | |
# X-Crop | |
if ($pictureWidth -gt $slideWidth) { | |
[float] $cropWidth = $pictureWidth - $slideWidth | |
$picture.PictureFormat.Crop.ShapeLeft += $cropWidth / 2 | |
$picture.PictureFormat.Crop.ShapeWidth -= $cropWidth | |
$picture.Left = 0 | |
} elseif ($pictureWidth -lt $slideWidth) { | |
[float] $deltaWidth = ($slideWidth - $pictureWidth) / 2 | |
$picture.Left = $deltaWidth | |
} | |
# Y-Crop | |
if ($pictureHeight -gt $slideHeight) { | |
[float] $cropHeight = $pictureHeight - $slideHeight | |
switch ($Align) { | |
"top" { | |
$picture.PictureFormat.Crop.ShapeHeight -= $cropHeight | |
$picture.Top = 0 | |
} | |
"bottom" { | |
$picture.PictureFormat.Crop.ShapeTop += $cropHeight | |
$picture.Top = 0 | |
} | |
default { | |
$picture.PictureFormat.Crop.ShapeTop += $cropHeight / 2 | |
$picture.PictureFormat.Crop.ShapeHeight -= $cropHeight | |
$picture.Top = 0 | |
} | |
} | |
} elseif ($pictureHeight -lt $slideHeight) { | |
[float] $deltaHeight = ($slideHeight - $pictureHeight) / 2 | |
$picture.Top = $deltaHeight | |
} | |
if (![string]::IsNullOrEmpty($Cite)) { | |
Add-Textbox -Slide $Slide -Text $Cite -Margin @(700, 50, 50, 440) -FontSize 18 | |
} | |
return $picture | |
} | |
function Add-Code { | |
param ( | |
[object] $Slide, | |
[string] $Code | |
) | |
[hashtable] $args = @{ | |
Slide = $Slide; | |
Text = $Code.Trim(); | |
ForeColor = 0xFFFFFF; | |
BackColor = 0x562401; | |
AsciiFont = "Consolas"; | |
FontSize = 24; | |
} | |
[object] $textbox = Add-Textbox @args | |
return $textbox | |
} | |
function Add-List { | |
param ( | |
[object] $Slide, | |
[string[]] $Items | |
) | |
if ($Items -eq $null) { | |
return $null | |
} | |
[hashtable] $args = @{ | |
Slide = $Slide; | |
Text = [string]::Join("`n", $Items); | |
Bullet = $true; | |
} | |
[object] $textbox = Add-Textbox @args | |
return $textbox | |
} | |
function New-Slide { | |
param ( | |
[object] $Presentation, | |
[Xml.XmlElement] $Xml | |
) | |
if ($Presentation -eq $null -or $Xml -eq $null) { | |
return $null | |
} | |
[object] $slide = $Presentation.Slides.Add($Presentation.Slides.Count + 1, 12) | |
$slide.Select() | |
if ($Xml.h1 -ne $null) { | |
$DefaultTitleHeight = 150 | |
Add-SlideTitle -Slide $slide -Title $Xml.h1 | |
} else { | |
$DefaultTitleHeight = 0 | |
} | |
[string] $cite = [string]::Empty | |
if ($Xml.q -ne $null) { | |
$cite = $Xml.q.cite | |
$Xml = $Xml.q | |
} | |
if ($Xml.img -ne $null) { | |
Add-Image -Slide $slide -Image $Xml.img.src -Align $Xml.img.align -Cite $cite | |
} | |
if ($Xml.pre.code -ne $null) { | |
Add-Code -Slide $slide -Code $Xml.pre.code | |
} | |
if ($Xml.ul -ne $null) { | |
Add-List -Slide $slide -Items $Xml.ul.li | |
} | |
return $slide | |
} | |
try { | |
[xml] $xml = [xml] (Get-Content $Source) | |
} catch { | |
throw | |
} | |
[object] $powerPoint = New-Object -ComObject PowerPoint.Application | |
# $powerPoint.Visible = [Microsoft.Office.Core.MsoTriState]::msoTrue | |
[object] $presentation = $powerPoint.Presentations.Add($true) | |
[string] $title = $xml.html[1].head.title | |
[string] $author = $xml.html[1].head.meta | ?{ $_.name -eq 'author' } | Select-Object -ExpandProperty content | |
[object] $titleSlide = Add-TitleSlide -Presentation $presentation -Title $title -Author $author | |
$xml.html[1].body.section | ?{ $_ -ne $null } | %{ | |
[int] $i = $presentation.Slides.Count | |
[int] $n = $xml.html[1].body.section.Count | |
[float] $p = 100.0 * $i / $n | |
Write-Progress -Activity "Generate PowerPoint presentation" -Status "$i / $n Completed" -PercentComplete $p | |
[void] (New-Slide -Presentation $presentation -Xml $_) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<!-- "Power{Point,ed by,Shell}" by @miriyagi at 1st #lt_titech --> | |
<title>Power{Point,ed by,Shell}</title> | |
<meta name="Author" content="八木 悠(@miriyagi)" /> | |
</head> | |
<body> | |
<section> | |
<h1>八木 悠(@miriyagi)</h1> | |
<img src="puffy.png" /> | |
</section> | |
<section> | |
<img src="love.jpg" align="top" /> | |
</section> | |
<section> | |
<h1>研究で使うのは当然 Linux</h1> | |
<img src="linux.jpg" align="fit" /> | |
</section> | |
<section> | |
<h1>研究に明け暮れる幸せな日々</h1> | |
<img src="research.jpg" /> | |
</section> | |
<section> | |
<h1>研究発表でも</h1> | |
<img src="presentation.jpg" align="top" /> | |
</section> | |
<section> | |
<h1>使うのは当然 Beamer</h1> | |
<img src="beamer.png" /> | |
</section> | |
<section> | |
<h1>そんな CUI を愛する学生生活を満喫</h1> | |
<img src="terminal.jpg" /> | |
</section> | |
<section> | |
<h1>とある企業に入社</h1> | |
<img src="hataraitara.jpg" align="top" /> | |
</section> | |
<section> | |
<h1>渡されるのは Windows, Office, ...</h1> | |
<img src="windows.png" /> | |
</section> | |
<section> | |
<h1>みんな GUI しかないじゃない!</h1> | |
<img src="mami.jpg" /> | |
</section> | |
<section> | |
<h1>そんなある日……</h1> | |
<img src="start.png" /> | |
</section> | |
<section> | |
<h1>PowerShell ならできるじゃない!</h1> | |
<img src="startps.png" /> | |
</section> | |
<section> | |
<img src="logo.png" /> | |
</section> | |
<section> | |
<h1>やりたいこと</h1> | |
<ul> | |
<li>PowerPoint プレゼンテーションを CUI で作りたい</li> | |
<li>XHTML を PowerShell 経由で変換できたら素敵</li> | |
<li>文字だけではなく画像やリストにも対応したい</li> | |
</ul> | |
</section> | |
<section> | |
<h1>PowerShell とは</h1> | |
<ul> | |
<li>Microsoft 製のシェルスクリプト言語</li> | |
<li>Windows 7 から標準搭載</li> | |
<li>最新版は PowerShell 3.0</li> | |
</ul> | |
</section> | |
<section> | |
<h1>オブジェクト指向シェル</h1> | |
<pre><code> | |
PS C:\Users\Yagi> cd Temp | |
PS C:\Users\Yagi\Temp> $x = ls | |
PS C:\Users\Yagi\Temp> $x | |
ディレクトリ: C:\Users\Yagi\Temp | |
Mode LastWriteTime Length Name | |
---- ------------- ------ ---- | |
-a--- 2012/11/24 15:15 114145 bing.png | |
-a--- 2012/11/24 16:17 400645 logo.png | |
-a--- 2012/11/24 14:48 54711 love.png | |
PS C:\Users\Yagi\Temp> $x[1].Name.ToUpper() | |
LOGO.PNG | |
</code></pre> | |
</section> | |
<section> | |
<h1>PowerShell ISE</h1> | |
<img src="ise.png" /> | |
</section> | |
<section> | |
<h1>XHTML (XML) の読み込み</h1> | |
<pre><code> | |
$xml = [xml] (Get-Content "source.xhtml") | |
$sections = $xml.html.body.section | |
$x = $sections[0].h1 | |
</code></pre> | |
</section> | |
<section> | |
<h1>プレゼンテーションの新規作成</h1> | |
<pre><code> | |
$powerPoint = New-Object -ComObject PowerPoint.Application | |
$presentation = $powerPoint.Presentations.Add($true) | |
</code></pre> | |
</section> | |
<section> | |
<h1>白紙スライドの追加</h1> | |
<pre><code> | |
$slide = $presentation.Slides.Add($index, 12) | |
</code></pre> | |
</section> | |
<section> | |
<h1>テキストボックスの作成</h1> | |
<pre><code> | |
$ori = [Microsoft.Office.Core.MsoTextOrientation]::msoTextOrientationHorizontal | |
# $ori = 1 | |
$textBox = $slide.Shapes.AddTextBox($ori, $x, $y, $width, $height) | |
$textBox.TextFrame.TextRange.Text = "YUKI.N>みえてる?" | |
</code></pre> | |
</section> | |
<section> | |
<h1>テキストボックスの変更</h1> | |
<pre><code> | |
# フォント | |
$box.TextFrame.TextRange.Font.NameFarEast = "メイリオ" | |
$box.TextFrame.TextRange.Font.NameAscii = "Consolas" | |
$box.TextFrame.TextRange.Font.Size = 24 | |
# 色 | |
$box.Fill.BackColor.RGB = 0x562401 # -> RGB(0x01, 0x24, 0x56) | |
$box.TextFrame.TextRange.Font.Color.RGB = 0xFFFFFF | |
</code></pre> | |
</section> | |
<section> | |
<h1>画像の埋め込み</h1> | |
<pre><code> | |
$picture = $Slide.Shapes.AddPicture("image.png", $false, $true, $posX, $poxY, $width, $height) | |
</code></pre> | |
</section> | |
<section> | |
<h1>画像のトリミング</h1> | |
<pre><code> | |
if ($pictureWidth -gt $slideWidth) { | |
$cropWidth = $pictureWidth - $slideWidth | |
$picture.PictureFormat.Crop.ShapeLeft += $cropWidth / 2 | |
$picture.PictureFormat.Crop.ShapeWidth -= $cropWidth | |
$picture.Left = 0 | |
} | |
</code></pre> | |
</section> | |
<section> | |
<h1>後はこれを組み合わせれば……</h1> | |
<img src="combination.png" /> | |
</section> | |
<section> | |
<h1>完成!</h1> | |
<img src="code.png" align="top" /> | |
</section> | |
<section> | |
<h1>デモ</h1> | |
</section> | |
<section> | |
<h1>まとめ</h1> | |
<ul> | |
<li>XHTML を PowerPoint プレゼンテーションにする PowerShell スクリプトを書いたよ!</li> | |
<li>Windows でも(意外と)CUI で遊べるよ!</li> | |
</ul> | |
</section> | |
<section> | |
<h1>今後の課題</h1> | |
<ul> | |
<li>スライドテンプレートの利用</li> | |
<li>複数コンテンツへの対応</li> | |
<li>他のタグへの対応</li> | |
</ul> | |
</section> | |
<section> | |
<img src="owari.png" align="fit" /> | |
</section> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment