#!/usr/bin/perl -w use strict; use Regexp::Common; use Win32::PowerPoint; use warnings FATAL => qw{ uninitialized }; ################################################################ #### This is a parser that converts the simple subset of beamer into a PowerPoint file. #### Note that inner-nested formatting options are currently only supported for: #### Italic, bold, text size, color. ################################################################ my $PP= qr/$RE{balanced}{-parens=>'\{ \}'}/ms; ## how to match nested curly parens (defined($ARGV[0])) or die "Need beamer .tex file as input."; ## a default file ## read the entire contents of the file $_ = slurpfile( $ARGV[0] ); ## comments not avaliable in ppt conversion s/[^\\]\%(.*)$//gm; s/\\n($PP)//g; s/\\begin\{N\}/\\begin\{N\}\{/g; s/\\end\{N\}/\}\\end\{N\}/g; s/\\begin\{N\}($PP)\\end\{N\}//g; # invoke (or connect to) PowerPoint my $ppt = Win32::PowerPoint->new; ################################################################ #### user settings ################################################################ #set text sizes and margins my $titleSize = 36; my $textSize = 24; my $topMargin = 100; #location of the topmost textbox my $textMargin = 25; my $defaultColor = [0,0,0]; #[255,255,255] format my $defaultAlign = 'left'; #left, center, or right # set presentation-wide information $ppt->new_presentation( #background_forecolor => [255,255,255], #background_backcolor => [0,0,0], #pattern => 'Shingle', ); #set bullet point shape, in decimal unicode my $bulletPoint = 8658; # and master footer if you prefer (optional) #1 if yes; 0 if no $ppt->set_master_footer( visible => 1, text => 'My Slides', slide_number => 1, datetime => 1, datetime_format => 'MMMMyy', ); # other variables my $top; my $tempOption; my $option = {bold => 0, italic => 0,size => $textSize, left => 50, top =>$top, width=>600, color => [0,0,0], alignment => 'left', superscript => 0, subscript => 0}; my $titleOption = { size => 36, left => 50, top =>50, width=>600, alignment => 'center', bold => 1}; ################################################################ #### Save title page ################################################################ s/\\title\{(.*?)\}//; my $title = $1; s/\\subtitle\{(.*?)\}//g; my $subtitle= $1; s/\\author\{(.*?)\}//g; my $author= $1; s/\\institute[\{\[](.*?)[\}\]]//g; my $institute= $1; s/\\date\{(.*?)\}//g; my $date= $1; ################################################################ #### special character definitions #### TODO: perhaps move this to a separate file? ################################################################ #greek alphabet s/\\alpha/\x{03B1}/g; s/\\beta/\x{03B2}/g; s/\\gamma/\x{03B3}/g; s/\\delta/\x{03B4}/g; s/\\epsilon/\x{03B5}/g; s/\\zeta/\x{03B6}/g; s/\\eta/\x{03B7}/g; s/\\theta/\x{03B8}/g; s/\\iota/\x{03B9}/g; s/\\kappa/\x{03BA}/g; s/\\lambda/\x{03BB}/g; s/\\mu/\x{03BC}/g; s/\\nu/\x{03BD}/g; s/\\xi/\x{03BE}/g; s/\\omicron/\x{03BF}/g; s/\\pi/\x{03C0}/g; s/\\rho/\x{03C1}/g; s/\\sigma/\x{03C3}/g; s/\\tau/\x{03C4}/g; s/\\upsilon/\x{03C5}/g; s/\\phi/\x{03C6}/g; s/\\chi/\x{03C7}/g; s/\\psi/\x{03C8}/g; s/\\omega/\x{03C9}/g; s/\\Alpha/\x{0391}/g; s/\\Beta/\x{0392}/g; s/\\Gamma/\x{0393}/g; s/\\Delta/\x{0394}/g; s/\\Epsilon/\x{0395}/g; s/\\Zeta/\x{0396}/g; s/\\Eta/\x{0397}/g; s/\\Theta/\x{0398}/g; s/\\Iota/\x{0399}/g; s/\\Kappa/\x{039A}/g; s/\\Lambda/\x{039B}/g; s/\\Mu/\x{039C}/g; s/\\Nu/\x{039D}/g; s/\\Xi/\x{039E}/g; s/\\Omicron/\x{039F}/g; s/\\Pi/\x{03A0}/g; s/\\Rho/\x{03A1}/g; s/\\Sigma/\x{03A3}/g; s/\\Tau/\x{03A4}/g; s/\\Upsilon/\x{03A5}/g; s/\\Phi/\x{03A6}/g; s/\\Chi/\x{03A7}/g; s/\\Psi/\x{03A8}/g; s/\\Omega/\x{03A9}/g; #currencies s/\\pound/\x{00A3}/g; s/\\euro/\x{20AC}/g; s/\\yen/\x{00A5}/g; s/\\cent/\x{00A2}/g; #math expressions s/\\infinity/\x{223E}/g; s/\\infty/\x{223E}/g; s/\\leftrightarrow/\x{21D4}/g; s/\\leftarrow/\x{21D0}/g; s/\\rightarrow/\x{21D2}/g; s/\\int/\x{222B}/g; s/\\cdot/\x{00B7}/g; s/\\perp/\x{22A5}/g; s/\\frac12/\x{00BC}/g; s/\\frac14/\x{00BD}/g; s/\\frac34/\x{00BE}/g; s/\\cross/\x{2715}/g; s/\\florin/\x{0192}/g; s/\\ne/\x{2260}/g; s/\\equiv/\x{2261}/g; s/\\sim/\x{007E}/g; s/\\lt/\x{003C}/g; s/\\gt/\x{003E}/g; s/\\le/\x{2264}/g; s/\\ge/\x{2265}/g; s/\\in/\x{2208}/g; s/\\emptyset/\x{2205}/g; s/\\forall/\x{2200}/g; s/\\pm/\x{00B1}/g; s/\\times/\x{2715}/g; s/\\prod/\x{220F}/g; s/\\sum/\x{2211}/g; s/\\sqrt/\x{221A}/g; s/\\approx/\x{2248}/g; s/\\prop/\x{223D}/g; #other symbols s/\\spadesuit/\x{2660}/g; s/\\clubsuit/\x{2663}/g; s/\\heartsuit/\x{2665}/g; s/\\diamondsuit/\x{2666}/g; s/\\copyright/\x{00A9}/g; s/\\curlyleft/\x{007D}/g; s/\\curlyright/\x{007B}/g; s/\\vert/\x{01C0}/g; s/\\qquad/\t/g; s/\\textcolor\{red\}/\\textred/g; s/\\textcolor\{blue\}/\\textblue/g; s/\^/\\\^/g; s/\_/\\\_/g; s/\\\$/internalDollarSign/g; s/\$//g; #kills LaTeX equation parameters s/internalDollarSign/\$/g; ################################################################ #### write content pages #### TODO: expand color set? only red, black, blue now. #### TODO: enable text formatting in tables (18pt black for now.) ################################################################ my $ParagraphsCount = 0; while(/\\begin[\s]*\{frame\}/){ s/^.*?\\begin\{frame\}//s; $ppt->new_slide; $top = $topMargin; $option->{size} = $textSize; #write title page if(s/\\titlepage//) { $ppt->add_text($title, { size => 36, left => 100, top =>150, width=>600, alignment => 'center', bold => 1}); $ppt->add_text($subtitle, {size => 32, left => 250, top =>200, width=>400}); $ppt->add_text($author, { size => 24, left => 450, top =>280, width=>200, alignment => 'right'}); $ppt->add_text($institute, { size => 24, left => 450, top =>300, width=>200, alignment => 'right'}); $ppt->add_text($date, { size => 24, left => 450, top =>320, width=>200, alignment => 'right'}); } my $header = ""; if(/^[\s]*\{/) {s/^[\s]*\{(.*)\}//; $header = $1;} $ppt->add_text('',$titleOption); my $tempTextSize = $textSize; $textSize = $titleSize; print STDERR "[Writing slide: $header]\n"; parse($header,$titleOption) if($header ne ""); $textSize = $tempTextSize; $ppt->add_text('', $option); $ParagraphsCount = 0; while(!/^\s*?\\end[\s]*\{frame\}/s) { if (s/^[\n \s]*?\\begin[\s]*\{enumerate\}//) { my $number = 1; $number = $1+1 if (s/\\setcounter[\s]*\{enumi\}\{(.*)\}//); #enumerations while(!s/^[\n \s]*\\end[\s]*\{enumerate\}//) { if (s/^[\n \s]*\\begin[\s]*\{itemize\}//) { while (!s/^[\n \s]*\\end[\s]*\{itemize\}//){ s/^[\n \s]*\\item//; s/^[\n]*(.*)\n//; my $bullet = $ppt->slide->Shapes($ppt->slide->Shapes->Count)->TextFrame->TextRange->Paragraphs($ParagraphsCount+1)->ParagraphFormat->Bullet; $bullet->{Visible} = 1; $bullet->{RelativeSize} = 1.25; $bullet->{Character}= $bulletPoint; my $indent = $ppt->slide->Shapes($ppt->slide->Shapes->Count)->TextFrame->TextRange->Paragraphs($ParagraphsCount+1); $indent->{IndentLevel} = 3; parse($1,$option);$ppt->insert_after("\n",$tempOption);$ParagraphsCount++; }} if (s/^[\n \s]*\\end[\s]*\{enumerate\}//) {last;} if (s/^[\n \s]*\\item/$number. /) {$number ++;} if (s/^[\n \s]*(.*)\n//) {parse($1,$option);$ppt->insert_after("\n",$tempOption);$ParagraphsCount++;} } } #itemizations elsif (s/^[\n \s]*?\\begin[\s]*\{itemize\}//) { while (!s/^[\n \s]*\\end[\s]*\{itemize\}//){ s/^[\n \s]*\\item//; s/^[\n]*(.*)\n//; my $bullet = $ppt->slide->Shapes($ppt->slide->Shapes->Count)->TextFrame->TextRange->Paragraphs($ParagraphsCount+1)->ParagraphFormat->Bullet; $bullet->{Visible} = 1; $bullet->{RelativeSize} = 1.25; $bullet->{Character}= $bulletPoint; my $indent = $ppt->slide->Shapes($ppt->slide->Shapes->Count)->TextFrame->TextRange->Paragraphs($ParagraphsCount+1); $indent->{IndentLevel} = 3; parse($1,$option);$ppt->insert_after("\n",$tempOption);$ParagraphsCount++; } } #tables elsif (s/^[\n \s]*?\\begin[\s]*\{tabular\}($PP)//) { $ppt->slide->Shapes->AddTable(1,1,100,$top); my $table = $ppt->slide->Shapes( $ppt->slide->Shapes->Count )->Table; my $tableAlign = $1; #reads the alignment instructions my $numRows = 1; my $numCols = 1; my $currRow = 1; my $currCol = 1; my $input = ''; while(! s/^[\n \s]*?\\end[\s]*\{tabular\}// && s/^[\n \s]*\\tr\{(.*)\}/$1/) { if ($currRow > $numRows) {$table->Rows->Add; $numRows++;} $currCol = 1; while (! s/^\s*\}// && s/^[\n \s]*\\td($PP)//) { if ($currCol > $numCols) {$table->Columns->Add; $numCols++;} $input = $1; $input =~ s/\{(.*)\}/$1/; $table->Cell($currRow, $currCol)->Shape->TextFrame->TextRange->{Text} = $input; $currCol++;} $currRow++; } $top += $textMargin*$numRows; } #regular parsing else { s/^[\n \s]*(.*)//; parse($1, $option); $ppt->insert_after("\n",$tempOption);$ParagraphsCount++; } $top += $textMargin; } } ################################################################ #### Clean up, check for existing file ################################################################ print STDERR "\nSlides successfully generated. Name the file: \n"; my $fileName; my $yn; my $readInput = 0; while ($readInput == 0) { chomp($fileName=); $fileName .= ".pptx"; if (-e $fileName) { print STDOUT "File already exists. Overwrite? Y/N \n"; chomp($yn=); if ($yn eq "Y") { $ppt->save_presentation($fileName); $readInput = 1; } else { print STDERR "Name the file: \n"; next; } } $ppt->save_presentation($fileName); $readInput = 1; } $ppt->close_presentation; # PowerPoint closes automatically ################################################################ #### subroutines ################################################################ #read the whole file sub slurpfile { my $v= ""; foreach my $fname (@_) { print STDERR "[reading $fname]\n"; local $/=undef; open(FIN, $fname) or die "cannot open '$fname': $!\n"; $v .= ; # slurp the entire file close(FIN); } return $v; } #does some simple parsing of italic/bold/color/size to support nested formatting #expects substring as $_[0], option as $_[1] sub parse { my $storeOption; #save formatting options $tempOption->{bold} = $_[1]->{bold}; $tempOption->{italic} = $_[1]->{italic}; $tempOption->{alignment} = $_[1]->{alignment}; $tempOption->{left} = $_[1]->{left}; $tempOption->{width} = $_[1]->{width}; $tempOption->{color} = $_[1]->{color}; $tempOption->{superscript} = $_[1]->{superscript}; $tempOption->{subscript} = $_[1]->{subscript}; $storeOption->{bold} = $_[1]->{bold}; $storeOption->{italic} = $_[1]->{italic}; $storeOption->{alignment} = $_[1]->{alignment}; $storeOption->{left} = $_[1]->{left}; $storeOption->{width} = $_[1]->{width}; $storeOption->{color} = $_[1]->{color}; $storeOption->{superscript} = $_[1]->{superscript}; $storeOption->{subscript} = $_[1]->{subscript}; my $string = $_[0]; my $tempString = ""; my $modify = ""; $string =~ s/\\pause/\n/g; $string =~ s/\\bigskip/\n/g; $string =~ s/\\\\/\n/g; $string =~ s/\\\%/\%/g; $string =~ s/\\\$/\$/g; #break into single arguments while($string ne ''){ if ($string =~ s/^[\n \s]*?\\tiny//){ $tempOption->{size} = $textSize-12;} elsif ($string =~ s/^[\n \s]*?\\scriptsize//){ $tempOption->{size} = $textSize-9;} elsif ($string =~ s/^[\n \s]*?\\footnotesize//){ $tempOption->{size} = $textSize-6;} elsif ($string =~ s/^[\n \s]*?\\small//){ $tempOption->{size} = $textSize-3;} elsif ($string =~ s/^[\n \s]*?\\normalsize//){ $tempOption->{size} = $textSize;} elsif ($string =~ s/^[\n \s]*?\\large//){ $tempOption->{size} = $textSize+3;} elsif ($string =~ s/^[\n \s]*?\\Large//){ $tempOption->{size} = $textSize+6;} elsif ($string =~ s/^[\n \s]*?\\LARGE//){ $tempOption->{size} = $textSize+9;} elsif ($string =~ s/^[\n \s]*?\\huge//){ $tempOption->{size} = $textSize+12;} elsif ($string =~ s/^[\n \s]*?\\Huge//){ $tempOption->{size} = $textSize+15;} #$tempOption->{size} = $option->{size}; if ($string =~ s/^\\([a-z \^ \_]*)($PP)//){ $modify = $1; $tempString = $2; $tempString =~ s/\{(.*)\}$/$1/; $tempOption->{bold} = 1 if($modify eq 'textbf'); $tempOption->{italic} = 1 if ($modify eq 'textit'); $tempOption->{alignment} = 'center' if($modify eq 'textcenter'); $tempOption->{alignment} = 'left' if($modify eq 'textleft'); $tempOption->{alignment} = 'right' if($modify eq 'textright'); $tempOption->{color} = [255,0,0] if($modify eq 'textred'); $tempOption->{color} = [0,0,255] if($modify eq 'textblue'); $tempOption->{subscript} = 1 if($modify eq '_'); $tempOption->{superscript} = 1 if($modify eq '^'); parse($tempString,$tempOption); #restore $tempOption->{bold} = $storeOption->{bold}; $tempOption->{italic} = $storeOption->{italic}; $tempOption->{size} = $storeOption->{size}; $tempOption->{subscript} = $storeOption->{subscript}; $tempOption->{superscript} = $storeOption->{superscript}; $tempOption->{left} = $storeOption->{left}; $tempOption->{width} = $storeOption->{width}; $tempOption->{color} = $storeOption->{color};} elsif ($string =~ /\\/){ $string =~ s/(.*)\\/\\/; $ppt->insert_after($1,$tempOption);} else { if ($string ne '') {$ppt->insert_after($string,$tempOption); $string = '';} } } }