pdf-slave
Tool that compiles haskintex (TeX with embedded Haskell) files into PDF documents.
Templates are described in YAML format and can define dependencies.
Features:
-
Input JSON file for htex
file that holds template varying data.
-
Option for packing a template in all-in YAML bundle.
-
Support for template dependencies that include:
- Bibtex files
- Images, listings, other static files.
- Other
.htex
templates that compiles into TeX and includes into parent template.
- Other
.htex
templates that compiles into PDF and includes into parent template.
Description
In many real-world systems we need to produce reports, cheques and other documents
to users. One possible solution is rendering LaTeX files, but it is not flexible
enough as you need to solve burden with generation of LaTeX file from varying inputs,
manage static files like images and handling dependencies between pieces of document.
The new tool takes the burden away. Now all static files and generation code is
located within a single template project, that describes how to build your PDF document in a declaretive way.
Common work flow with pdf-slave:
-
Develop template from several LaTeX files with embedded Haskell (thanks to haskintex
tool)
-
Pack the template project into bundle all-in file.
-
Distribute the bundle (for instance to your service server).
-
Call pdf-slave with bundle and inputs in JSON file to generate PDF document.
Template reference
Common template consists of several files:
-
template_input.json
- Input data for template in JSON format.
{
"line-width": 2,
"spiral-precision": 0.01,
"spiral-interval": [0,4],
"spiral-a": 0.1,
"spiral-b": 4
}
-
template.htex
- TeX/LaTeX with embedded Haskell that reads input data from
file template.json
. You have to provide code at the beginning of file that reads
the inputs, like that:
Make a Helper.hs
file in directory with template:
module Helper where
import qualified Data.ByteString.Lazy as BS
import Data.Aeson
import System.IO.Unsafe
defaultInput :: FromJSON a => a
{-# NOINLINE defaultInput #-}
defaultInput = case unsafePerformIO $ fmap eitherDecode' $ BS.readFile "input.json" of
Left e -> error (show e)
Right a -> a
Next in your .htex
file:
\begin{document}
\begin{haskellpragmas}
{-# LANGUAGE OverloadedStrings #-}
\end{haskellpragmas}
\begin{writehaskell}
import Data.Aeson
import Helper
data Input = Input {
lineWidth :: Double
, spiralPrecision :: Double
, spiralInterval :: (Double, Double)
, spiralA :: Double
, spiralB :: Double
}
instance FromJSON Input where
parseJSON (Object o) = Input
<$> o .: "line-width"
<*> o .: "spiral-precision"
<*> o .: "spiral-interval"
<*> o .: "spiral-a"
<*> o .: "spiral-b"
inpt :: Input
inpt = defaultInput
\end{writehaskell}
Note: The tool copies JSON with inputs to build folder with .htex twice, once with
name specified at input
key in template description and secondly at fixed input.json
.
-
template.yaml
- description of template and its dependencies.
name: template01 # name of template
input: template01_input.json # name of input file
body: template01.htex # name of .htex file
dependencies: {} # dependency tree (see below)
haskintex-opts: [] # additional flags to haskintex
For input Helper.hs
you should make a record to inform pdf-slave
that the module should be copied to build directory:
dependencies:
Helper.hs:
type: other
Dependencies
There are 4 different types of dependencies:
-
other
- static files that don't require any processing. They could be images,
listings etc. Example of YAML configuration:
dependencies:
lambda.png: # name of dependency is equal to filename relative to parent template
type: other # marks type of dependency
code/demo.hs:
type: other
See examples/example02 for full example.
-
bibtex
- bibliography files that require additional passes with bibtex
. Example of YAML configuration:
dependencies:
biblio.bib: # name of dependency is equal to filename relative to parent template
type: bibtex # marks type of dependency
See examples/example03 for full example.
-
template
- other .htex
templates that are included in parent via \\input{...}
or \\include{..}
. Example of YAML configuration:
dependencies:
dep1: # name of dependency defines subfolder where the output tex file is located
type: template # marks type of dependency
name: dep1
input: dep1_input.json
body: dep1.htex
dep2:
type: template
name: dep2
body: dep2.htex
dependencies:
lambda.png:
type: other
code/demo.hs:
type: other
See examples/example04 for full example.
Note that code/demo.hs
subdependency should be included as dep2/code/demo.hs
in dep2.htex
as dep2.tex
is inlined into parent.
-
template_pdf
- other .htex
templates that are included as PDFs into parent template. Example of YAML configuration:
dependencies:
template01: # name of dependency defines subfolder where the output tex file is located
type: template_pdf # marks type of dependency
name: template01
input: template01_input.json
body: template01.htex
template02:
type: template_pdf
name: template02
body: template02.htex
dependencies:
lambda.png:
type: other
code/demo.hs:
type: other
See examples/example05 for full example.
When you work with dependencies you have two options how to handle inputs:
-
Define dependency inputs in its own file:
dependencies:
dep1:
type: template
name: dep1
input: dep1_input.json # private input file
body: dep1.htex
-
Define dependency inputs in parent file:
name: template06
input: template06_input.json # contains inputs for dep1
body: template06.htex
dependencies:
dep1:
type: template
name: dep1
body: dep1.htex
Contents of template06_input.json
:
{
"dep1": {
"line-width": 2,
"spiral-precision": 0.01,
"spiral-interval": [0,4],
"spiral-a": 0.1,
"spiral-b": 4
}
}
Note that key of subsection must be equal to name of dependency.
See examples/example06 for full example.
Making bundles
One can pack all .htex
, .json
, .yaml
and all dependencies in single YAML
bundle that can be easily distributed, transmitted between services and stored:
cd examples/template01
pdf-slave --template template01.yaml --output template01_bundle.yaml pack
As modification of such bundles isn't handy, one can unpack bundle:
pdf-slave --template template01_bundle.yaml --output template01_directory unpack
Rendering of bundles is handled with the same command that is used for ordinary
templates.
Compilation
You need:
Compilation with stack:
git clone https://github.com/NCrashed/pdf-slave.git
cd pdf-slave
stack install
Compilation with cabal:
git clone https://github.com/NCrashed/pdf-slave.git
cd pdf-slave
cabal sandbox init
cabal install --dependencies-only
cabal install
Running examples
You need:
-
LaTeX distribution (for instance, texlive or miktex)
-
stack or GHC+Cabal (yes, you need GHC to evaluate templates at runtime)
-
pdf-slave
and haskintex
executables in PATH
Stack users:
cd examples/template01
stack install aeson HaTeX
stack exec -- pdf-slave --template template01.yaml --output output.pdf pdf && xdg-open output.pdf
Cabal users:
cd examples/template01
cabal sandbox init
cabal install aeson HaTeX
pdf-slave --template template01.yaml --output output.pdf pdf && xdg-open output.pdf
Docker build
-
Run cook_doocker.sh
script. This will build two images, one pdf-slave-build
for compilation of binaries and the second one pdf-slave
for production usage.
-
Usage:
docker run -it --rm -v $(pwd)/examples/template01:/data/examples pdf-slave pdf --template examples/template01.yaml --output examples/output.pdf
xdg-open examples/template01/output.pdf
- Or download precompiled container from Docker Hub.
Credits