Sunday, November 25, 2007

PowerPoint -> PDF (Part 2)

After writing a VBA Macro to convert a PowerPoint with animation into a flat PowerPoint without animation (suitable for conversion to PDF), I wrote my talk. After writing my talk I realised I not only needed elements to appear on a click, but also disappear and reappear. Unfortunately my original macro script did not do that, so I've updated it with two new features:

Safer

The original code removed all slides which were not hidden to return to the previous state. Unfortunately, if you haven't yet run the AddElements script, that results in it deleting all your slides! Undo saves the day, but it would be nicer for RemElements to be a bit more considerate. This version tags all the slides it creates, then RemElements simply removes those with the appropriate tag - hopefully this removes the obvious "Doh!" moment from this tool.

Works with show/hide/reshow

The original code used the Shape.AnimationSettings properties to detect what happened to objects. Unfortunately, this property only records the first action associated with an object - I suspect in the past PowerPoint only allowed one action and this is merely here for compatibility. To get the full range of events you need to use the Slide.TimeLine property. Writing the code I ran into two issues: (1) objects with silly names; (2) mutability.

Objects With Silly Names

Some objects have properties which don't do what you might think! Effect.EffectType = msoAnimEffectAppear implies that the animation is to appear, but if Effect.Exit = msoTrue then this is the disappear effect! I was confused by this one for quite a while.

In order to solve all the naming problems, I made extensive use of the Visual Basic debugger included with Office, which puts many other debuggers to shame. It is at least 1000x better than any Haskell debugger I've ever seen, and equally far ahead of things like GDB. Microsoft's software may be maligned, but their debuggers are truly fantastic! It really does allow an entirely new style of development, and is particularly suited to dipping into a new API without having a large learning curve.

Mutability

Mutability is a bad idea. If you delete a shape, while you are iterating over a collection of shapes, you silently skip the element that comes after the deleted shape! If you delete a shape, and that shape is the subject of a transition, then that corresponding transition is deleted. If you change the Shape.AnimationSettings.Animate to msoFalse, this removes any associated transitions. All this means that to try and iterate over something starts to become a challenge!

The problem with mutability in this particular task is that it is unclear what is changing and when, leading to subtle bugs and lots of debugging. Again, the debugger helped, but not as much as before - having to single-step through quite deep properties is not a particularly fun task.

The Code

And here is the code, to be use as in the same way to the previous post.


Option Explicit

Sub AddElements()
Dim shp As Shape

Dim i As Integer, n As Integer
n = ActivePresentation.Slides.Count
For i = 1 To n
Dim s As Slide
Set s = ActivePresentation.Slides(i)
s.SlideShowTransition.Hidden = msoTrue

Dim max As Integer: max = AnimationElements(s)
Dim k As Integer, s2 As Slide
For k = 1 To max
Set s2 = s.Duplicate(1)
s2.Name = "AutoGenerated: " & s2.SlideID
s2.SlideShowTransition.Hidden = msoFalse
s2.MoveTo ActivePresentation.Slides.Count

Dim i2 As Integer, h As Shape
Dim Del As New Collection
For i2 = s2.Shapes.Count To 1 Step -1
Set h = s2.Shapes(i2)
If Not IsVisible(s2, h, k) Then Del.Add h
Next
Dim j As Integer
For j = s.TimeLine.MainSequence.Count To 1 Step -1
s2.TimeLine.MainSequence.Item(1).Delete
Next
For j = Del.Count To 1 Step -1
Del(j).Delete
Del.Remove j
Next
Next
Next
End Sub

'is the shape on this slide visible at point this time step (1..n)
Function IsVisible(s As Slide, h As Shape, i As Integer) As Boolean

'first search for a start state
Dim e As Effect
IsVisible = True
For Each e In s.TimeLine.MainSequence
If e.Shape Is h Then
IsVisible = Not (e.Exit = msoFalse)
Exit For
End If
Next

'now run forward animating it
Dim n As Integer: n = 1
For Each e In s.TimeLine.MainSequence
If e.Timing.TriggerType = msoAnimTriggerOnPageClick Then n = n + 1
If n > i Then Exit For
If e.Shape Is h Then IsVisible = (e.Exit = msoFalse)
Next
End Function

'How many animation steps are there
'1 for a slide with no additional elements
Function AnimationElements(s As Slide) As Integer
AnimationElements = 1
Dim e As Effect
For Each e In s.TimeLine.MainSequence
If e.Timing.TriggerType = msoAnimTriggerOnPageClick Then
AnimationElements = AnimationElements + 1
End If
Next
End Function

Sub RemElements()
Dim i As Integer, n As Integer
Dim s As Slide
n = ActivePresentation.Slides.Count
For i = n To 1 Step -1
Set s = ActivePresentation.Slides(i)
If s.SlideShowTransition.Hidden = msoTrue Then
s.SlideShowTransition.Hidden = msoFalse
ElseIf Left$(s.Name, 13) = "AutoGenerated" Then
s.Delete
End If
Next
End Sub


As before, no warranty, and please do backup first!

25 comments:

Roger said...

Great script! Exactly the functionality I have been looking for. If the script could handle appearing/disappearing bullets in a list WITHIN a text object as well there would be nothing left to ask for. Keep up the good work!

Kari said...

A great script indeed! I'd like to use it with work-related presentations, but since you did not mention any license, I'm not sure if it's OK to use it. Could you add the kind of license that you intended the script to be used with? (public domain i.e. free to use for anything, GPL, BSD, commercial, or something completely different)

Neil Mitchell said...

Roger: I may extend it in such a direction, if it turns out not to be too hard. I'll probably put it on a proper web page and as a downloadable file, to make it a bit easier.

Kari: BSD with no advertising - the source code is free, you are free to do what you want with it. If BSD doesn't meet your needs, let me know.

Anonymous said...

Hi Neil,

just another quick "thanks" for your code - it pretty much solves exactly the problem I was right now dealing with :-)

I will keep an eye on this page to not miss the new homepage for the VB Script...

Cheers,
Christian

Anonymous said...

It works fine except for:

When you add multi-step animations to a text block sentence by sentence, this coding cannot recognize that. It will treat the whole text block as 1 step animation.

Anonymous said...

Dear Neil,

is there any chance to modify the code such that the numbers of the slides are preserved? My use case is to provide slides for students on our web page, and it would be extremely useful to still be able to point them to a particular slide from the original talk after the conversion has been performed...

Thanks,
Christian (christian at uni-paderborn de)

Ophir Kra-Oz said...

Great Work.
Would be nice to hide objects that are hidden.
I might try and add it when I have some free time.
Worked nicely on Office 2007, BTW.

Neil Mitchell said...

@Christian and Ophir: Quite a lot of people seem interested in this, so I'll set up a darcs repo, a web page, and attempt to make a proper release. Once I've got a proper source code repo for it, then it will be easier to add/accept features.

John W. Myrna said...

A great utility! Echo others in that the only thing I had to handle manually was bulleted items within a list.

I use that mode a lot. A list of itmes, each one revealed on a mouse click.

Martin said...

You sir, have just saved my day!

Wonderful workaround to the pdf animation problem - thank you so much for sharing it with us less VBA savvy PP users :)

Anonymous said...

After copying AddElements into a module and tryin to run, I get an error "compile error: method or data member not found" running on mac word 2004. Is there something I'm missing?

Anonymous said...

Thank you so much for the script.
I have an important presentation in a week and you help a lot!

Ulrike said...

Many many thanks also from me! The only thing I've done manually is move the hidden slides to the back so that the numbering starts with "1" in the pdf. Would be nice but by no means crucial if that would happen automatically.

JAE said...

Thank you! I just used this with my pptx file (Microsoft Office 2007) and it worked beautifully!

Thank you again!

Marc Vayssières said...

Very useful script! Thank you. Marc

Anonymous said...

You just saved my job!!!! When i needed to send a powerpoint with custom animations to a group of tech-challenged colleagues (it would be cruel to have them install powerpoint viewer) your code was a life saver. thank you. thank you. thank you.

Hardeep Singh said...

Thanks, its a nice script and works well for me

Anonymous said...

Neil,
Thank you so much for sharing this script. It works great and saves me so much time!

Best,
Ram

Sri said...

Neil, Great work.

Here's is one more add-in. It works perfectly with 07.

http://www.dia.uniroma3.it/~rimondin/downloads.php

Abhilash said...

This is brilliant. Was looking for this and was thinking about contacting my IT to get this script written. Helped me a huge amount of time and effort.
Thanks a mill!!!

Anonymous said...

Saved my day: thank you very very much! ;)

Unknown said...

Great script! Thanks! One problem is that page numbering is not retained. Any suggestions???

DHorl said...

Hi Neil.

I had difficulty to get this into Powerpoint2010. Cant find the button to paste this code.

Your assistance is highly appreciated

Anonymous said...

Hello,

Thanks for this tip that is really useful.

Just a drawback: the incrementation of the slides numbers :-( (my final slide is slide # 99/15)

Do you have a trick to avoid this?

Thanks in advance ;-)

Anonymous said...

Hello,

Thanks for this tip that is really useful.

Just a drawback: the incrementation of the slides numbers :-( (my final slide is slide # 99/15)

Do you have a trick to avoid this?

Thanks in advance ;-)