Показаны сообщения с ярлыком latex. Показать все сообщения
Показаны сообщения с ярлыком latex. Показать все сообщения

воскресенье, 13 апреля 2014 г.

Несколько трюков с пакетом Latex Listings

Здесь я хочу показать три трюка с пакетом Listings, которые мне очень пригодились при определении “языка” для подсветки действий пользователя внутри командных оболочек и REPLов. Этот виртуальный язык, который я назвал shelloutput, может быть использован в плагине publish-helper. Некоторые сведения о реализации подсветки shelloutput в Listings приведены в разделе Highlighting shells and REPLs на главной странице проекта в github, там же приведен пример с картинками. Итак, определение псевдоязыка shelloutput — это и есть первый трюк, о котором я расскажу. Постановка задачи такова: обеспечить декоративное выделение блоков текста, изображающих действия пользователя внутри любых командных оболочек и REPLов, а также разделение текста с вводом пользователя (промпта) и выводом оболочки. Первая часть задачи решается без определения свойств языка и, соответственно, без участия пакета Listings. Простейший способ — определить новое окружение, внутри которого использовать какой-либо рамочный пакет, например пакет Framed.
\usepackage{framed}
\newenvironment{Leftbar}{
  \setlength\parskip{0pt}
  \setlength\OuterFrameSep{4pt}
  \setlength\partopsep{-\topsep}
  \begin{leftbar}
    \scriptsize
}{\end{leftbar}}
Это окружение, которое я назвал Leftbar, поскольку оно использует стиль leftbar из пакета Framed, должно находиться в преамбуле документа. Теперь, чтобы выделить блок текста с действиями пользователя в оболочке, нужно обернуть этот текст в тэги \begin{Leftbar} ... \end{Leftbar}. Перейдем к решению второй части задачи. Нам нужно определить новый язык, который мы сможем использовать в окружении lstlisting, которое, в свою очередь, мы будем помещать внутрь окружения Leftbar. Пусть основной стиль (basicstyle) языка shelloutput будет относиться к выводу оболочки, а стиль для ввода оболочки мы определим отдельно. Самое сложное здесь — это понять, как мы (и Listings) будем отличать ввод оболочки от вывода: промпты у всех оболочек разные и могут отсутствовать вообще. Поэтому мы не будем ориентироваться на промпт, а введем собственный виртуальный промпт, который будем вставлять в начало каждой строки, соответствующей вводу оболочки — ведь мы то знаем, где у нее ввод и вывод! В Listings есть опция moredelim, с помощью которой мы определим внешний вид ввода оболочки, передав ей наш виртуальный промпт (пусть это будет три бара и пробел — ‘||| ’) с флагами il, которые устанавливают, что Listings не должен показывать виртуальный промпт (i), и установки будут действовать в пределах всей строки (l). Вот как это может выглядеть в преамбуле документа:
\usepackage{listings}
\definecolor{shellpromptcolor}{HTML}{000000}
\definecolor{shelloutputcolor}{HTML}{666666}
\lstdefinelanguage{shelloutput}
  {basicstyle=\color{shelloutputcolor}
    \scriptsize
    \ttfamily\itshape,
   moredelim=[il][\color{shellpromptcolor}\upshape]{|||\ }}
В данном случае вывод оболочки будет подсвечен серым цветом и иметь наклонное начертание, в то время как ввод будет черным и прямым. Давайте добавим сюда небольшой, но красивый бонус — декоративные переносы строк. Для этого в преамбулу документа нужно добавить
\usepackage{MnSymbol}
\lstset{prebreak=\raisebox{0ex}[0ex][0ex]
  {\ensuremath{\rhookswarrow}}}
\lstset{postbreak=\raisebox{0ex}[0ex][0ex]
  {\ensuremath{\rcurvearrowse\space}}}
Пример использования подсветки виртуального языка shelloutput можно найти в разделе An example на странице разработки проекта vim-publish-helper. В этом примере документ, представленный на втором изображении, генерируется не напрямую из документа TeX, а опосредованно, через pandoc flavoured markdown, но результат будет совпадать независимо от языка исходного документа. Второй трюк. Listings преобразует символ переноса (-) в знак минус. Это разумно для многих языков, однако для нашего псевдоязыка shelloutput это выглядит некрасиво, во всяком случае в опциях команд оболочки, таких как ls -l --inode. Вернуть исходный символ переноса достаточно просто — определив опцию literate={-}{-}1 в преамбуле документа или внутри окружения lstlisting, однако этот простой подход ломает настройки alsoletter={-} и breakatwhitespace для символов переноса, что приводит к возможности обрыва строк при переносе на этих символах, а это выглядит очень некрасиво. Лучше всего определить макрос lst@CCPutMacro для символа переноса (фактически переопределить его, так как он уже определен — и в этом-то и проблема — в исходном коде listings.sty). Определение следует поместить в нашем окружении Leftbar, чтобы оно не повлияло на другие языки. Теперь Leftbar будет выглядеть так:
\makeatletter
\newenvironment{Leftbar}{
  \setlength\parskip{0pt}
  \setlength\OuterFrameSep{4pt}
  \setlength\partopsep{-\topsep}
  \lst@CCPutMacro\lst@ProcessLetter {"2D}{\lst@ttfamily{-{}}{-{}}}
  \@empty\z@\@empty
  \begin{leftbar}
    \scriptsize
}{\end{leftbar}}
\makeatother
Третий трюк. Русские символы (и вообще UTF-8) в выводе (и вводе) языка shelloutput. Оказывается, Listings до сих пор не поддерживает нормальную обработку национальных символов в формате UTF. Если в shelloutput встретятся русские символы, то они будут переставлены всевозможными способами с удалением пробелов и добавлением неожиданных новых строк. Проблема известная и есть вариант ее решения с помощью опции literate (см. здесь и здесь). Однако, этот способ очень verbose — лично мне он не нравится. Более простое и рабочее решение — использовать опцию escapeinside=|| в объявлении lstlisting, а внутри блока с текстом обрамлять участки с проблемными символами соответствующими эскейп-символами, в данном случае барами (|) и, возможно, внутри них тэгом \verb, который не позволит слипнуться множественным пробелам.

четверг, 9 января 2014 г.

vim: плагин publish_helper

Работу, начатую здесь и здесь, решил оформить в виде плагина vim (страница на гитхабе и страница на www.vim.org). Полная документация доступна по ссылкам, а также внутри плагина. Плагин состоит из двух частей. Первая часть - это собственно скрипт на viml, в котором определены команды MakeHtmlCodeHighlight (переименованная и улучшенная MakeBlogArticle) и MakeTexCodeHighlight (тоже улучшенная по сравнению с первой реализацией). Вторая часть написана на haskell и представляет собой фильтр для pandoc (фильтры доступны в pandoc начиная с версии 1.12), который позволяет подменять исходные блоки кода (CodeBlock) внутри абстрактного синтаксического дерева, представляющего документ, на неформатированные блоки (RawBlock), содержащие подсвеченный с помощью vim код.

Для того, чтобы показать как это работает, возьмем оригинальный пример из руководства пользователя pandoc, в котором приведена классическая реализация быстрой сортировки на haskell, и добавим туда еще пару образцов кода.
### Original example from [*Pandoc User's Guide*](http://johnmacfarlane.net/pandoc/README.html#fenced-code-blocks)

~~~~ {#mycode .haskell .numberLines hl="vim" startFrom="100"}
qsort []     = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++
               qsort (filter (>= x) xs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

### Small C++ code sample

~~~~ {.cpp hl="vim"}
G4UIQt *  qtSession( dynamic_cast< G4UIQt * >( session ) );

if ( qtSession )
{
    qtSession->AddMenu( histoMenuHandle, histoMenuLabel );
    BuildMenuTree( qtSession, histoMenuHandle, gDirectory->GetList() );
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

### Content of my *~/.vimrc.pandoc*

~~~~ {#vimrc_pandoc .vim .numberLines hl="vim"}
syntax on

filetype on
filetype indent on
filetype plugin on
filetype plugin indent on

let g:lucius_style = 'light'
let g:lucius_contrast = 'high'
let g:lucius_contrast_bg = 'high'

set nocp    " for line breaks with backslashes
colorscheme lucius

let g:PhCtrlTrans = 0
let g:PhHtmlPreAttrs = 'style="white-space: pre-wrap;"'

runtime plugin/publish_helper.vim
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Это синтаксическая подсветка цветовой схемы lucius для типа файла pandoc, который будет доступен, если вы установите плагин для pandoc отсюда. Я вставил ее сюда из vim дословно с помощью команды MakeHtmlCodeHighlight.

Идем дальше. Сохраняем этот пример в файле example.md и генерируем HTML файл example.html с помощью pandoc и фильтра vimhl из нашего плагина.
pandoc --standalone -t html -F vimhl -o example.html example.md
Получаем файл example.html с таким содержимым:
 

Original example from Pandoc User's Guide

100  qsort []     = []
101  qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++
102                 qsort (filter (>= x) xs)

Small C++ code sample

G4UIQt *  qtSession( dynamic_cast< G4UIQt * >( session ) );

if ( qtSession )
{
    qtSession->AddMenu( histoMenuHandle, histoMenuLabel );
    BuildMenuTree( qtSession, histoMenuHandle, gDirectory->GetList() );
}

Content of my ~/.vimrc.pandoc

 1  syntax on
 2  
 3  filetype on
 4  filetype indent on
 5  filetype plugin on
 6  filetype plugin indent on
 7  
 8  let g:lucius_style = 'light'
 9  let g:lucius_contrast = 'high'
10  let g:lucius_contrast_bg = 'high'
11  
12  set nocp    " for line breaks with backslashes
13  colorscheme lucius
14  
15  let g:PhCtrlTrans = 0
16  let g:PhHtmlPreAttrs = 'style="white-space: pre-wrap;"'
17  
18  runtime plugin/publish_helper.vim

Это цвета схемы lucius с правильной подсветкой синтаксиса всех образцов. А теперь создадим аналогичный PDF документ.
pandoc --standalone -t latex -F vimhl -o example.tex example.md
Открываем файл example.tex и вставляем в преамбулу строки
\usepackage{color}
\usepackage{xcolor}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
\usepackage{framed}
\newenvironment{Shaded}{
  \definecolor{shadecolor}{rgb}{1.0, 1.0, 0.9}
  \setlength\parskip{0cm}
  \setlength\partopsep{-\topsep}
  \addtolength\partopsep{0.2cm}
  \begin{shaded}
    \scriptsize
}{\end{shaded}}
Зачем это нужно, вы можете прочесть в документации к плагину. Завершаем генерацию example.pdf.
latexmk -pdf example
Вот скриншот получившегося документа:

суббота, 26 октября 2013 г.

pandoc: экспорт блога в tex

Однажды, примерно полгода назад, я решил перевести статьи из этого блога в книжный формат. Для этого, в первую очередь, мне пришлось выбрать, в какой формат переводить исходные HTML страницы блога, а затем изучить доступные в сети программы, способные хорошо справиться с этой задачей. Собственно, с выбором формата не было никаких проблем: трудно предложить что-либо такое, что превосходило бы по возможностям формат PDF. Наиболее перспективной программой оказалась wkhtmltopdf (при этом, чтобы добиться достойного результата, мне пришлось скачать уже собранную версию программы, включающую важные патчи для Qt). Затем я создал в своей домашней директории новую директорию blog/, а внутри нее две поддиректории articles/ и images/. В первую поддиректорию я поместил все исходные HTML страницы блога, а во вторую - все изображения с этих страниц. Далее я разными автоматическими и полуавтоматическими способами добавил HTML хедеры и футеры в HTML страницы и исправил в них атрибуты src тэгов img, дабы они правильно указывали на изображения в директории articles/. После этого я написал простейший скрипт makepdf.sh, который генерировал PDF документ из этих страниц.
#!/bin/bash

BLOGNAME=Тех-детали
RESULT=blog.pdf
SRC=articles

WKHTMLTOPDF=wkhtmltopdf-amd64
FH_OPTS="-B 27 -T 24 --footer-line --header-line --footer-spacing 10 
 --header-spacing 10 --header-left $BLOGNAME --header-right [title]
 --footer-right [page]"
EXTRA_OPTS="--image-quality 100"
TOC_OPTS="toc --xsl-style-sheet wkhtmltopdf-toc.xsl"

OBJECTS="$SRC/boostspirit.html $SRC/c.html $SRC/cint.html $SRC/blog-post.html
 $SRC/subversion.html $SRC/cd-flac-mp3.html $SRC/boostspirit2.html
 $SRC/haskell.html $SRC/usb.html $SRC/subversion2.html
 $SRC/geant4-94-chargeexchangemc-aka-cexmc.html $SRC/f-spot.html $SRC/c2.html
 $SRC/9.html $SRC/vim-ctags-perl.html $SRC/tcl.html $SRC/haskell2.html
 $SRC/c-haskell.html $SRC/fedora-15-wicd.html $SRC/cern-root.html $SRC/vim.html
 $SRC/vim-2.html $SRC/firefox-6-thunderbird-6-fedora-14.html $SRC/ctags-c-c.html
 $SRC/make.html $SRC/texlive.html $SRC/ngrep.html $SRC/nginx.html
 $SRC/vim-taghighlight-tagbar.html $SRC/geant4-95.html $SRC/vim-powerline.html
 $SRC/nginx-i.html $SRC/nginx-ii.html $SRC/vim2.html
 $SRC/mate-compiz-fedora-17.html $SRC/nginx-myutil.html $SRC/blog-post2.html
 $SRC/shell-powerline.html $SRC/vim-vimwiki.html $SRC/diff.html
 $SRC/unicode-linux-fedora-17.html $SRC/powerline-linux.html $SRC/linux.html
 $SRC/vim-tex-minted.html $SRC/pdf-odp-ppt-latex.html
 $SRC/vim-xkb-switch-libcall.html $SRC/a-perl-script-for-gathering-blogger.html
 $SRC/fedora-17-fedora-18.html $SRC/vim3.html $SRC/c11-rvalue.html
 $SRC/tsung.html $SRC/c3.html $SRC/blog-post3.html"

$WKHTMLTOPDF $FH_OPTS $EXTRA_OPTS $TOC_OPTS $OBJECTS $RESULT
Я привожу его здесь только для того, чтобы показать, насколько гибок wkhtmltopdf: его опции перечислены в переменных FH_OPTS, EXTRA_OPTS и TOC_OPTS. Переменная OBJECTS представляет собой список всех файлов из директории articles/.

Результат оказался очень хорошим, предельно близким к оригиналу! После этого я на некоторое время успокоился, но затем меня снова начали терзать сомнения. В самом деле, несмотря на огромное количество опций wkhtmltopdf, отвечающих за внешний вид окончательного PDF документа, на выходе мы имеем примерно то же самое, что было в самом начале - груду неструктурированного мусора, которым невозможно управлять (редактировать, менять внешний вид структурных элементов и т.п.). Нужен другой формат, например tex.

Исследования показали, что есть такая программа pandoc, которая, судя по ее описанию, способна преобразовывать огромное количество форматов из одного в другой. В частности, она умеет выполнять преобразование из HTML в формат tex - то, что нужно! Установив pandoc, и опробовав его на одном из файлов в директории articles/, я увидел, что подсветка синтаксиса исходных кодов пропала. Я рассказывал, как я вставляю подсвеченный код в блог. По сути, он заворачивается внутрь тэгов <pre><tt> .. </tt></pre>, а конкретные цвета подставляет плагин TOhtml редактора vim на основании используемых для данного типа файла синтаксических правил. Кроме того, конвертор TOhtml заменяет пробелы на символы &nbsp;. Выяснилось, что pandoc не умеет подсвечивать составленные таким образом блоки HTML. Однако, он таки может подсвечивать исходный код, если внутри тэгов <pre> .. </pre> удалить все HTML тэги и символы неразрывного пробела, оставив голый исходный код, и снабдить тэг <pre> атрибутом class="тип_файла" или class="brush: тип_файла", где тип_файла - это синтаксический тип файла, например, cpp для C++. Просмотреть список поддерживаемых pandoc синтаксических типов файлов можно, введя команду
pandoc --version
Кроме того, вставленные когда-то хедеры и футеры HTML, оказались не нужны. Да и ссылки на изображения не нужны тоже. Поэтому я написал небольшой quick and dirty скрипт process_html.sh, который автоматизирует преобразование содержимого тэгов <pre>, удаляет хедэры и футеры HTML и ссылки на изображения.
#!/bin/bash 

CLASS=

while getopts :l: opt; do
  case $opt in
    l)  CLASS=$OPTARG ;;
    \?) echo "Invalid option: -$OPTARG" ;;
    :)  echo "Option -$OPTARG requires an argument."exit 1 ;;
  esac
done

shift $((OPTIND-1))

sed -e 's/\(^.*\)\?\(<\/\?pre>\)/\1\n\2\n/' "$@" |
sed -e 's/.*<\/\?html>\|<\/\?body>.*//; /<head>/,/<\/head>/d' \
    -e 's/<a href.*\+>\(<img[^>]\+src="\).*\(\/images[^"]\+"[^>]\+>\)/\1..\2/g' \
    -e '/^<tt>/,/^<\/tt>/s/<[^>]\+>//g; /^<pre>$/,/^<\/pre>$/s/&nbsp;/ /g' \
    -e "s/^<pre>$/<pre class=\"$CLASS\">/"
Я поместил этот скрипт в новую поддиректорию tex/, соседнюю с articles/ и images/. Единственная опция -l указывает значение для атрибута class тэга <pre>, то есть синтаксический тип исходных кодов в исходной странице HTML. Очевидно, на одной странице HTML могут находиться исходные коды разных синтаксических типов (например, на языках fortran и C++), поэтому этот скрипт нельзя рассматривать как полностью автоматический: ручная правка полученного результата может оказаться необходимой. Ну а в простейшем случае, достаточно выполнить
./process_html.sh -lcpp ../articles/c.html | pandoc --normalize -f html -o c.tex
чтобы из файла c.html получить файл c.tex. Файл c.tex не является законченным (standalone) документом tex. Его нужно вставить в шаблон. Я использовал следующий шаблон blog.tex:
\documentclass[12pt]{article}
% adjust page geometry
\usepackage[a4paper,vmargin={2cm,2cm},hmargin={2cm,2cm}]{geometry}

\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
\usepackage{ifxetex,ifluatex}
\usepackage{fixltx2e} % provides \textsubscript
% use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[utf8]{inputenc}
\else % if luatex or xelatex
  \ifxetex
    \usepackage{mathspec}
    \usepackage{xltxtra,xunicode}
    \usepackage{fontspec} % enables loading of OpenType fonts
    \usepackage{polyglossia} % support for languages

    % fonts:
    \defaultfontfeatures{Scale=MatchLowercase,Mapping=tex-text}
    \setmainfont{DejaVu Sans}
    \setsansfont{DejaVu Sans}
    \setmonofont{DejaVu Sans Mono}

    % Russian/English document:
    \usepackage{xecyr}
  \else
    \usepackage{fontspec}
  \fi
  \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
  \newcommand{\euro}{}
\fi
% use microtype if available
\IfFileExists{microtype.sty}{\usepackage{microtype}}{}
\usepackage{color}
\usepackage{xcolor}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
% Add ',fontsize=\small' for more characters per line
\usepackage{framed}
\definecolor{shadecolor}{rgb}{1.0, 1.0, 0.9}
\newenvironment{Shaded}{
  \begin{shaded}
    \scriptsize
}{\end{shaded}}
\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}}
\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}}
\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}}
\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}}
\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}}
\newcommand{\RegionMarkerTok}[1]{{#1}}
\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
\newcommand{\NormalTok}[1]{{#1}}
\usepackage{graphicx}
% Redefine \includegraphics so that, unless explicit options are
% given, the image width will not exceed the width of the page.
% Images get their normal width if they fit onto the page, but
% are scaled down if they would overflow the margins.
\makeatletter
\def\ScaleIfNeeded{%
  \ifdim\Gin@nat@width>\linewidth
    \linewidth
  \else
    \Gin@nat@width
  \fi
}
\makeatother
\let\Oldincludegraphics\includegraphics
{%
 \catcode`\@=11\relax%
 \gdef\includegraphics{\@ifnextchar[{\Oldincludegraphics}{\Oldincludegraphics[width=\ScaleIfNeeded]}}%
}%
\ifxetex
  \usepackage[setpagesize=false, % page size defined by xetex
              unicode=false, % unicode breaks when used with xetex
              xetex]{hyperref}
\else
  \usepackage[unicode=true]{hyperref}
\fi
\definecolor{linkcolor}{HTML}{AF8787}
\hypersetup{breaklinks=true,
            bookmarks=true,
            pdfauthor={},
            pdftitle={},
            colorlinks=true,
            citecolor=blue,
            urlcolor=blue,
            linkcolor=linkcolor,
            pdfborder={0 0 0}}
\urlstyle{same}  % don't use monospace font for urls
\setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}
\setlength{\emergencystretch}{3em}  % prevent overfull lines
\setcounter{secnumdepth}{0}
\ifxetex
  \newfontfamily\cyrillicfont{DejaVu Sans}
  \setmainlanguage{russian} 
  \setotherlanguage[variant=american]{english}
\fi

%\usepackage[raggedright]{titlesec}

\author{}
\date{}

\begin{document}

\tableofcontents

\include{boostspirit}
\include{c}
\include{cint}
\include{blog-post}
\include{subversion}
\include{cd-flac-mp3}
\include{boostspirit2}
\include{9}
\include{tsung}

\end{document}
Здесь много разных сложных объявлений до начала собственно документа. Откуда я их взял? Исходный шаблон можно получить, просто запустив pandoc с опцией -s (или --standalone) на каком-нибудь HTML файле, в котором присутствует исходный код с тэгом <pre> и атрибутом class (без этого не сгенерируется объявление среды Shaded). Конечно же, я изменил получившийся таким способом шаблон, во-первых, удалив содержимое между тэгами \begin{document} и \end{document} и заменив его на объявление \tableofcontents и список файлов в формате tex для вставки в документ (здесь их пока только девять: восемь самых старых постов и один из новых), а во-вторых, добавив определения для генерации кириллических шрифтов xetex из шрифтов DejaVu. Кроме того, я изменил геометрию страницы (второе объявление в скрипте), определил цвет shadecolor, цвет linkcolor (для элементов оглавления) и действия внутри среды Shaded (использовать блок shaded и меньший размер шрифта scriptsize).

Я решил, что исходные коды будут помещены на светлом бежевом фоне shadecolor, а работа внутри интерактивных пользовательских оболочек отмечена темной полосой слева. Во втором случае, из предварительно обработанного программой process_html.sh файла в формате HTML следует в соответствующих местах удалить из тэга <pre> атрибут class, а в файле в формате tex, созданном pandoc, обрамить соответствующие места (они будут ограничиваться тэгами \begin{verbatim} и \end{verbatim}) тэгами
\begin{leftbar}
\scriptsize

...

\end{leftbar}
После описанных мероприятий со всеми девятью файлами и запуска
latexmk -xelatex blog.tex
я получил вот такой файл blog.pdf. Результат хороший (при том, что Google испортил цвета шрифтов и ухудшил разрешение картинок при вставке в Google Docs, в оригинальном документе PDF все выглядит очень прилично). Даже при таком малоавтоматизированном процессе это было довольно-таки быстро.

Update. Продолжив добавлять отдельные страницы в PDF документ, столкнулся с интересной проблемой. В некоторых моих статьях есть примеры исходного кода на VimL (если кто не знает, так называется скриптовый язык для vim). Так вот, pandoc не умеет подсвечивать VimL. Оказывается, для подсветки языков pandoc использует haskell библиотеку highlighting-kate, которая, как не трудно догадаться, использует определения синтаксиса языков из KDE-шного редактора Kate. Kate поддерживает синтаксис около 200 языков, но не поддерживает VimL! Зафайлил им баг по этому поводу.

Ситуация, однако, оказалась не безвыходной. Я уже писал про латеховский пакет minted, который использует питоновскую библиотеку pygments для подсветки исходного кода. В общем, я решил в случае с исходным кодом на VimL использовать minted. Для этого в файл blog.tex добавляем строку
\usepackage{minted}
, а в файле, обработанном process_html.sh, удаляем атрибут class из соответствующего тэга <pre>. Передаем, как и раньше, этот файл на обработку pandoc, открываем полученный файл tex в редакторе, и заменяем соответствующие открывающие и закрывающие тэги \begin{verbatim} и \end{verbatim} на
\begin{Shaded}
\begin{minted}{vim}

...

\end{minted}
\end{Shaded}
Пакет minted требует, чтобы компилятор был запущен с опцией --shell-escape, поэтому собираем blog.pdf командой
latexmk -pdf -pdflatex="xelatex --shell-escape %O %S" blog.tex
В качестве бонуса имеем полное совпадение стиля подсветки исходного кода VimL со стилем подсветки других синтаксических типов. Это потому, что pandoc для рендеринга подсветки по умолчанию использует стиль той же pygments! (Опция pandoc, отвечающая за стиль подсветки, --highlight-style, может принимать и другие значения, но по умолчанию она равна pygments).

четверг, 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.