четверг, 13 декабря 2012 г.

Создание качественных PDF / ODP / PPT презентаций в latex

Для чего мне понадобилась корректная подсветка исходного кода Tex / minted в vim, о способе достижения которой я рассказывал здесь? Ну, например, для того, чтобы, как на то намекает название статьи, с особым комфортом и шиком генерировать качественные технические презентации прямо из vim! Например, вот такую:
Исходный код этой презентации на tex:
\documentclass{beamer}

\usepackage{lmodern}
\usepackage{minted}

\usetheme{CambridgeUS}
\usecolortheme{seahorse}

\definecolor{scriptbg}{rgb}{0.95,0.95,0.95}

\logo{\includegraphics[height=0.5cm]{MyCompanyLogo.png}}
\title {My Cool Presentation}
\author{IT Team}
\date{December 12, 2012}

\begin{document}
\maketitle

\begin{frame}
\frametitle{Our network}
\framesubtitle{(autogenerated from a dia file)}
\begin{center}
\includegraphics[width=0.8\paperwidth]{my_dia.mps}
\end{center}
\end{frame}

\begin{frame}[fragile]
\frametitle{Code samples from different languages}
\framesubtitle{(highlighted by Python Pygments via minted)}
\textbf{C++}
\begin{minted}[fontsize=\tiny,bgcolor=scriptbg,gobble=2]{c++}
  #include <iostream>
  int main( void )
  {
      std::cout << "Hello world" << std::endl;
      return 0;
  }
\end{minted}

\vspace{0.5cm}
\textbf{Python}
\begin{minted}[fontsize=\tiny,bgcolor=scriptbg,gobble=2]{python}
  #!/usr/bin/python
  print "Hello, World!"
\end{minted}

\vspace{0.5cm}
\textbf{Bash}
\begin{minted}[fontsize=\tiny,bgcolor=scriptbg,gobble=2]{sh}
  #!/usr/bin/bash
  echo Hello, World!
\end{minted}
\end{frame}

\end{document}
Кроме текста для построения презентации были использованы изображение-логотип компании MyCompanyLogo.png (надпись My Company Logo c красным кругом в правом нижнем углу каждого слайда) и файл в формате dia, из которого было автоматически сгенерировано векторное изображение my_dia.mps на втором слайде.

Кто же в ответе за всю эту красоту? Конечно же пакет beamer из репозитория tex: именно он создает структуру презентации и раскрашивает слайды в соответствии с темами, заданными командами \usetheme и \usecolortheme. Кстати, стандартные темы можно просмотреть на сайте Beamer Theme Matrix (но будьте осторожны - грузится он долго).

Пакет beamer создает прекрасные качественные презентации в формате PDF с перекрестными ссылками и панелью управления, расположенной внизу каждого слайда. А что делать, если нам нужен формат презентаций ODP OpenOffice / LibreOffice? Для этого нам нужно найти какой-нибудь качественный конвертор из PDF в ODP. В качестве движка конвертора прекрасно подходит программа pdftocairo из пакета Poppler (в моей Fedora 17 она входит в пакет rpm poppler-utils). Программа конвертора должна качественно, быстро и прозрачно преобразовать исходную презентацию в формате PDF в отдельные файлы PNG, а затем скомпоновать их в презентацию ODP.

На роль подобного менеджера подходит скрипт pdf2odp из пакета latexslides, однако он использует в качестве движка не Poppler, а Ghostscript, поэтому делает это, на мой взгляд, медленно и некачественно, кроме того, в нем нельзя задать желаемое разрешение PNG, которое всегда равно 300. Поэтому я написал патч для pdf2odp относительно текущей версии в репозитории, в котором реализованы опции по выбору движка конвертора (Ghostscript или pdftocairo из Poppler) и выходного разрешения картинок PNG. Вот этот патч:
--- bin/pdf2odp 2012-12-13 00:25:08.072750679 +0400
+++ bin/pdf2odp.new 2012-12-13 00:22:48.384713551 +0400
@@ -1,15 +1,48 @@
-#!/usr/bin/env python
+#!/usr/bin/python
+
+import sys, subprocess, os, glob, getopt
+
+def usage():
+    usage = """
+    Usage: %s [-x|--engine=] [-s|--scale=] pdffile [outfile]
+      -h --help        Prints help
+      -x --engine      Converter engine (gs or pdftocairo), default gs
+      -s --scale       Scale value, default 300
+    """
+    print usage %(os.path.basename(sys.argv[0]))
+
+# converter engine: gs or pdftocairo
+engine = 'gs'
+scale = 300
+pdffile = ''
+outfile = ''
+
+options, remainder = getopt.getopt(sys.argv[1:], 'hx:s:',
+                                   ['help','engine=', 'scale='])
+
+for opt, arg in options:
+    if opt in ('-x', '--engine'):
+        engine = arg
+    elif opt in ('-s', '--scale'):
+        scale = arg
+    elif opt in ('-h', '--help'):
+        usage()
+        sys.exit()
+
+if len(remainder) > 0:
+    pdffile = remainder[0]
+if len(remainder) > 1:
+    outfile = remainder[1]
 
-import sys, subprocess, os, glob
 # Check for odfpy and file argument
 try:
     from odf.opendocument import OpenDocumentPresentation
-    filename = sys.argv[1]
+    filename = pdffile
 except ImportError:
     print "You need odfpy, exiting."
     sys.exit(1)
 except IndexError:
-    print "Usage: %s pdfile [outfile]" %sys.argv[0]
+    usage()
     sys.exit(2)
 
 from odf.style import Style, MasterPage, PageLayout, PageLayoutProperties, \
@@ -27,21 +60,32 @@
     print "%s only accepts pdf files, exiting." %sys.argv[0]
     sys.exit(4)
 
-# Check for gs
+# Check for converter engine
 try:
-    subprocess.call(['gs', '-v'], stdout=subprocess.PIPE)
+    if engine == 'pdftocairo':
+        subprocess.call(['pdftocairo', '-v'], stdout=subprocess.PIPE)
+    else:
+        subprocess.call(['gs', '-v'], stdout=subprocess.PIPE)
 except OSError:
-    print "You need Ghostscript, exiting."
+    if engine == 'pdftocairo':
+        print "You need Poppler utils, exiting."
+    else:
+        print "You need Ghostscript, exiting."
     sys.exit(5)
 
-gs_args = ['gs', '-dNOPAUSE', '-dSAFER', '-dBATCH', '-sDEVICE=pngalpha',
-           '-r300', '-sOutputFile=tmp_%s_%%03d.png' %(file), filename]
+if engine == 'pdftocairo':
+    engine_args = ['pdftocairo', '-png', '-scale-to', '%s' %(scale), filename,
+                   'tmp_%s_' %(file)]
+else:
+    engine_args = ['gs', '-dNOPAUSE', '-dSAFER', '-dBATCH',
+                   '-sDEVICE=pngalpha', '-r%s' %(scale),
+                   '-sOutputFile=tmp_%s_%%03d.png' %(file), filename]
            
-# Try to run gs
-print 'Converting %s to images using gs\n' %filename
-result = subprocess.Popen(gs_args)
+# Try to run converter engine
+print 'Converting %s to images using %s\n' %(filename, engine)
+result = subprocess.Popen(engine_args)
 if result.wait():
-    print '\nRunning gs failed with the error above, exiting.'
+    print '\nRunning %s failed with the error above, exiting.' %engine
     sys.exit(6)
 
 print "\nDone..."
@@ -90,7 +134,7 @@
     imageframe.addElement(Image(href=href))
 
 # Save file
-file = os.path.splitext(sys.argv[2])[0] if len(sys.argv) > 2 else file
+file = os.path.splitext(outfile)[0] if len(outfile) > 0 else file
 doc.save(file, True)
 print "Presentation saved as %s.odp" %file
 
Для преобразования картинок PNG в ODP pdf2odp использует пакет odfpy, так что его тоже необходимо установить.

Преобразовать презентацию из ODP в PPT нам поможет OpenOffice или LibreOffice. И у того и у другого есть пакетный режим конвертации, который, как это ни странно, не работает, если запущен хотя бы один графический инстанс офисного приложения (sic!), поэтому команда make ppt, о которой речь пойдет ниже, не сделает ничего и завершится при этом без ошибки, если у вас открыто какое-либо офисное приложение из указанных пакетов!

Итак, речь зашла о make. Извольте, это Makefile, который делает все:
# Produce main.pdf in output directory specified in latexmkrc

GREP                =   grep
SED                 =   sed
DIA                 =   dia
LATEXMK             =   latexmk
MPOST               =   mpost
PDF2ODP             =   pdf2odp
OFFICE              =   libreoffice

LATEXMKRC           =   ./latexmkrc
PDF_MODE_PTN        =   ^\s*$$pdf_mode\s*=\s*
OUT_EXT             =   $(shell case \
                        `$(GREP) '$(PDF_MODE_PTN)' $(LATEXMKRC) 2>/dev/null | \
                        $(SED) 's/$(PDF_MODE_PTN)\([0-3]\).*/\1/'` \
                        in ([1-3]) echo pdf ;; (*) echo dvi ;; esac)
OUT_DIR_PTN         =   ^\s*$$out_dir\s*=\s*
OUT_DIR             =   $(shell \
                        $(GREP) '$(OUT_DIR_PTN)' $(LATEXMKRC) 2>/dev/null | \
                        $(SED) 's/$(OUT_DIR_PTN)["\x27]\(.*\)["\x27].*/\1/')

ifeq ($(strip $(OUT_DIR)),)
    OUT_DIR         =   .
endif

MAIN                =   main
TARGET              =   $(OUT_DIR)/$(MAIN).$(OUT_EXT)
ODP                 =   $(OUT_DIR)/$(MAIN).odp
PPT                 =   $(OUT_DIR)/$(MAIN).ppt

TEX_SOURCES         =   $(wildcard *.tex)
DIA_SOURCES         =   $(wildcard *.dia)
EPS_IMAGES          =   $(wildcard *.eps)
DIA_MP_SOURCES      =   $(DIA_SOURCES:.dia=.mp)
DIA_MPS_IMAGES      =   $(DIA_SOURCES:.dia=.mps)

DIA_MP_LOGS         =   $(DIA_SOURCES:.dia=.log)
DIA_MPX_FILES       =   $(DIA_SOURCES:.dia=.mpx)
DIA_MP_TRANS_FILES  =   $(DIA_MP_LOGS) $(DIA_MPX_FILES)
DIA_INTERMEDIATES   =   $(DIA_MP_SOURCES) $(DIA_MP_TRANS_FILES)
DIA_ALL_PRODUCTS    =   $(DIA_INTERMEDIATES) $(DIA_MPS_IMAGES)

MAIN_BBL            =   $(OUT_DIR)/$(MAIN).bbl


.PHONY: all clean clean-all odp ppt

.SECONDARY: $(DIA_MP_SOURCES)

all: $(TARGET)

odp: $(ODP)

ppt: $(PPT)

%.mp: %.dia
    $(DIA) -e $@ $<

%.mps: %.mp
    $(MPOST) -s 'outputtemplate="%j.mps"' $<

$(TARGET): $(EPS_IMAGES) $(DIA_MPS_IMAGES) $(TEX_SOURCES)
    $(LATEXMK) $(MAIN)

$(ODP): $(MAIN).pdf
    $(PDF2ODP) -x pdftocairo -s 1600 $(MAIN).pdf

$(PPT): $(ODP)
    $(OFFICE) --headless --convert-to ppt --outdir $(OUT_DIR) $(ODP)

clean:
    $(LATEXMK) -c
    rm -f $(DIA_INTERMEDIATES)

clean-all:
    $(LATEXMK) -C
    rm -f *-eps-converted-to.pdf $(DIA_ALL_PRODUCTS) $(MAIN_BBL) $(ODP) $(PPT)
К нему прилагается файл latexmkrc (он должен находится в той же директории, где находится Makefile, т.е. в нашей рабочей директории), который необходим для правильной работы latexmk:
$pdf_mode = 1;                                  # use pdflatex
$pdflatex = 'pdflatex --shell-escape %O %S'     # needed by minted
Команда make без параметров строит презентацию в формате PDF, make odp - презентацию в формате ODP, а make ppt - презентацию в формате PPT.

В данном Makefile определены абстрактные правила преобразования форматов, поэтому его можно использовать в разных проектах, связанных с tex. Главная переменная, которую, как предполагается, должен определять пользователь, это MAIN - она определяет имена исходного файла tex и сгнерированных файлов презентаций. В нашем примере предполагается, что исходный файл tex называется main.tex и, соответственно, сгенерированные файлы презентаций будут иметь имена main.pdf, main.odp и main.ppt.

2 комментария:

  1. Чем не устроила подсветка синтаксиса от lstlistings? Там тоже можно задавать стили и цвета. И без всякого питона)

    ОтветитьУдалить
  2. Для меня особой роли не играло, что использовать. Просто решил попробовать minted.

    ОтветитьУдалить