#!/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=<STDIN>);
$fileName .= ".pptx";
if (-e $fileName)
{
print STDOUT "File already exists. Overwrite? Y/N \n";
chomp($yn=<STDIN>);
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 .= <FIN>;                    # 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 = '';}
    }
}
}





