A non-WYSIWYG, customizable, highly sugary syntax, document generator inspired by noWord (from which certain portions of code are taken) using reportlab as backend processor.
Similarly to a LaTeX document, the engine uses plain text input instructions and generates a consistent PDF document. This allows to:
- Use the generator in a headless environment / scripts
- Easily keep track of the input files with versioning tools as they are structured plain text (git-able, commits, branches, pull-requests, etc)
- Easily split the document in several input files and
include
them in a flexible way to keep the document organized and clean - Define include-able custom set of templates and styles that can be used in several documents, keeping a consistent look for all the documentation
The inputs to provide to the engine is a list of so-called blocks
, which are simple key-value maps (python dicts). Each one has a type
which will be recognized and processed by a module and can archieve the following purposes:
- structural operations: configure engine, load modules, define page layouts, define font styles, ...
- actual document content: insert titles, text, tables, lists, images, ...
A set of templates is provided that define the most common layouts and styles so that it is easy to create a simple document.
The build process is done in two stages:
The "build" time when the engine processes a list of blocks using the available modules and transforms them into reportlab objects, called "flowable" objects. These can be directly processed and rendered by the reportlab mechanism.
The "render" time when the reportlab engine uses the flowable objects and generates the final PDF document. This is usually done several times so that the indexing items, such as the table of content, are up to date with all the document content (like a LaTeX document).
To use it, you will need the following python packages:
- reportlab
- Pillow
- pdfrw
- pyaml
- python-dateutil
- python-magic
pip install reportlab Pillow pdfrw pyaml python-dateutil python-magic
The easiest way to use it is to run the main.py
script, which has the following interface: main.py src [dst]
- src is your document entry point, which should be a yaml or json file. Yaml is preferred because it is the easiest human-editable structured format.
- dst is the path to the pdf to create, if nothing is provided, the file
document.pdf
will be created in the current directory.
Usually, the document entry point is a file that will include all the other document files. Look at the demos and the templates for examples on how to create a simple document.
The engine itself can also be easily used in any python script as the only required steps to generate a document are:
e = engine.Engine() # Instanciate the engine
build = e.processBlocks(blocks, "/path/to/document/working/dir") # Feed blocks
e.build(build, "/path/to/pdf/to/generate") # Generate PDF
As demonstrated by the demos, the document structure to feed to the engine should be:
- load desired modules
- define page layout(s) available in the document
- define font styles available in the document
- define page decorations if desired
- set default font styles for the paragraphs, titles, table of contents
- set document metadata
- insert actual content
Which is easy to split in multiple files using the include
mechanism.
Currently, all the following block types can be used:
- absolute: draw block in absolute position instead of inserting it in the document flow
- appendFrame: add blocks to an existing frame
- bookmark: create a bookmark that references this document position, can be used to jump to any particular position with a link
- chapter: alias for
title
- data: set provided data as resource
- decoration: define a new decoration
- disableDecoration: stop decorating the document with specified decoration
- enableDecoration: start decorating the document with specified decoration from this point (repeatable)
- exec: run python code on the fly, even build drawable reportlab blocks
- font: create a new font style
- frame: define the frame and build contained blocks
- function: define a new block processor that will be used on certain block types, just like normal modules
- hspace: insert a horizontal space
- if: insert a conditional block
- image: insert an image
- img: alias for
image
- include: include file content or a whole folder, can be recursive
- line: insert a horizontal line
- list: insert a bullet or numbered list
- loop: loop over a resource and render blocks at each iteration
- meta: set metadata of the generated PDF document
- module: load module, set module attributes or call module method
- newpage: insert a page break, using current page style or a custom one
- page: define a new page style
- pagestyle: set initial default page style
- paint: draw something directly on canvas in python
- pdf: include a whole PDF document or a page subset
- qr: insert a QR code in the document
- resource: load a json, yaml or csv file as resource
- space: insert a space, both horizontal and vertical
- table: insert a table
- template: load a predefined template
- vspace: insert a vertical space
- text: insert a paragraph
- txt: alias for
txt
- textstyle: change default font to use from this point
- title: insert a title
- titleFormat: change title number formatting
- titleNum: change title level number format
- titleStyle: change the default chapter font styles
- tocEntry: insert a manual entry in the table of contents
These blocks are stored in the extra
module because they are more specific or even in development, therefore considered optional. They often require to install additional python modules to be used, see dedicated readme for each:
- ๐ ๏ธ bib: load LaTeX bib bibliography database file
- ๐ ๏ธ bibliography: generate a bibliography
- ๐ ๏ธ footnote: insert a footnote on the current page
- ๐ ๏ธ fn: alias for
footnote
- formula: insert a math formula, LaTeX style
- ๐ ๏ธ lst: insert a code listing with syntax highlighting
๐ ๏ธ: Those modules are work in progress, somewhat usable but with care.
The engine has a special data dict called resources
which is often referenced and used by modules. This dict is intended to contain various information that are directly accessible by all modules and within the text and various blocks. For example, it is possible to load a data file like a json or a csv using the Resource module and use the data as the rows of a table.
Here is a non exhaustive list of what can be found in the resources:
- data file loaded by the Resource module
chapters
dict which contains all chapter information managed by the Title modulebookmarks
dict which contains bookmarks managed by the Bookmarklists
dict which contains list counters managed by the Listloops
dict which contains current loop data while iterating managed by the Loopbuild
dict which contains information of current build, like current datetime, current page, page count managed by the PageCounter- ...
Here is a non exhaustive list of where the resource dict can be used:
- anywhere in the text using a special markup, processed through TextProcessor
- as rows of a table
- as items of a list
- as items to iterate with a loop
- as data source of modules that exec python snippets (Condition, Decorator, Painter, ...)
- in any module / callback that receives the engine
- ...
For a more in-depth view of the modules, engine and reportlab tricks, see here.
GNU GPLv3, see license.md
- Add a plot block to insert data charts using matplotlib: should be implemented as
extra
module, find the right amount of sugar to add over matplotlib - Allow to instanciate multiple module instances and call a specific one in a block: not sure if it is relevant, find a usecase first
- Blocks to ease variable/resource manipulation (set, eval, remove, update dict, update list, ...)
- Moar blocks: square, link area, line, arc, shape, circle, barcode ...
- Implement footnotes: currently not available in reportlab, so it might be really hard to do anything usable without HUGE hacks
- Allow to use resources as data source for Image, QRCode, Metadata: easy one
- Allow to reset pageNum/pageTot within the document: this is not trivial (can't be fixed by simple resource manipulation) as the special
pageNum
andpageTot
variables are computed at build time, so it requires to mess up with the PageCounter during build.