summaryrefslogtreecommitdiff
path: root/paper/lua-filters/diagram-generator/diagram-generator.lua
diff options
context:
space:
mode:
Diffstat (limited to 'paper/lua-filters/diagram-generator/diagram-generator.lua')
-rw-r--r--paper/lua-filters/diagram-generator/diagram-generator.lua295
1 files changed, 295 insertions, 0 deletions
diff --git a/paper/lua-filters/diagram-generator/diagram-generator.lua b/paper/lua-filters/diagram-generator/diagram-generator.lua
new file mode 100644
index 0000000..aef2ea4
--- /dev/null
+++ b/paper/lua-filters/diagram-generator/diagram-generator.lua
@@ -0,0 +1,295 @@
+--[[
+ This Lua filter is used to create images with or without captions from
+ code blocks. Currently PlantUML, GraphViz, Tikz, and Python can be
+ processed. For further details, see README.md.
+
+ Thanks to @floriandd2ba and @jgm for the initial implementation of
+ the PlantUML filter, which I used as a template. Thanks also @muxueqz
+ for the code to generate a GraphViz image.
+]]
+
+-- The PlantUML path. If set, uses the environment variable PLANTUML or the
+-- value "plantuml.jar" (local PlantUML version). In order to define a
+-- PlantUML version per pandoc document, use the meta data to define the key
+-- "plantumlPath".
+local plantumlPath = os.getenv("PLANTUML") or "plantuml.jar"
+
+-- The Inkscape path. In order to define an Inkscape version per pandoc
+-- document, use the meta data to define the key "inkscapePath".
+local inkscapePath = os.getenv("INKSCAPE") or "inkscape"
+
+-- The Python path. In order to define a Python version per pandoc document,
+-- use the meta data to define the key "pythonPath".
+local pythonPath = os.getenv("PYTHON")
+
+-- The Python environment's activate script. Can be set on a per document
+-- basis by using the meta data key "activatePythonPath".
+local pythonActivatePath = os.getenv("PYTHON_ACTIVATE")
+
+-- The Java path. In order to define a Java version per pandoc document,
+-- use the meta data to define the key "javaPath".
+local javaPath = os.getenv("JAVA_HOME")
+if javaPath then
+ javaPath = javaPath .. package.config:sub(1,1) .. "bin"
+ .. package.config:sub(1,1) .. "java"
+else
+ javaPath = "java"
+end
+
+-- The dot (Graphviz) path. In order to define a dot version per pandoc
+-- document, use the meta data to define the key "dotPath".
+local dotPath = os.getenv("DOT") or "dot"
+
+-- The pdflatex path. In order to define a pdflatex version per pandoc
+-- document, use the meta data to define the key "pdflatexPath".
+local pdflatexPath = os.getenv("PDFLATEX") or "pdflatex"
+
+-- The default format is SVG i.e. vector graphics:
+local filetype = "svg"
+local mimetype = "image/svg+xml"
+
+-- Check for output formats that potentially cannot use SVG
+-- vector graphics. In these cases, we use a different format
+-- such as PNG:
+if FORMAT == "docx" then
+ filetype = "png"
+ mimetype = "image/png"
+elseif FORMAT == "pptx" then
+ filetype = "png"
+ mimetype = "image/png"
+elseif FORMAT == "rtf" then
+ filetype = "png"
+ mimetype = "image/png"
+end
+
+-- Execute the meta data table to determine the paths. This function
+-- must be called first to get the desired path. If one of these
+-- meta options was set, it gets used instead of the corresponding
+-- environment variable:
+function Meta(meta)
+ plantumlPath = meta.plantumlPath or plantumlPath
+ inkscapePath = meta.inkscapePath or inkscapePath
+ pythonPath = meta.pythonPath or pythonPath
+ pythonActivatePath = meta.activatePythonPath or pythonActivatePath
+ javaPath = meta.javaPath or javaPath
+ dotPath = meta.dotPath or dotPath
+ pdflatexPath = meta.pdflatexPath or pdflatexPath
+end
+
+-- Call plantuml.jar with some parameters (cf. PlantUML help):
+local function plantuml(puml, filetype)
+ local final = pandoc.pipe(javaPath, {"-jar", plantumlPath, "-t" .. filetype, "-pipe", "-charset", "UTF8"}, puml)
+ return final
+end
+
+-- Call dot (GraphViz) in order to generate the image
+-- (thanks @muxueqz for this code):
+local function graphviz(code, filetype)
+ local final = pandoc.pipe(dotPath, {"-T" .. filetype}, code)
+ return final
+end
+
+-- Compile LaTeX with Tikz code to an image:
+local function tikz2image(src, filetype, additionalPackages)
+
+ -- Define file names:
+ local outfile = string.format("./tmp-latex/file.%s", filetype)
+ local tmp = "./tmp-latex/file"
+ local tmpDir = "./tmp-latex/"
+
+ -- Ensure, that the tmp directory exists:
+ os.execute("mkdir -p tmp-latex")
+
+ -- Build and write the LaTeX document:
+ local f = io.open(tmp .. ".tex", 'w')
+ f:write("\\documentclass{standalone}\n\\usepackage{tikz}\n")
+
+ -- Any additional package(s) are desired?
+ if additionalPackages then
+ f:write(additionalPackages)
+ end
+
+ f:write("\\begin{document}\n")
+ f:write(src)
+ f:write("\n\\end{document}\n")
+ f:close()
+
+ -- Execute the LaTeX compiler:
+ pandoc.pipe(pdflatexPath, {'-output-directory', tmpDir, tmp}, '')
+
+ -- Build the basic Inkscape command for the conversion:
+ local baseCommand = " --without-gui --file=" .. tmp .. ".pdf"
+ local knownFormat = false
+
+ if filetype == "png" then
+
+ -- Append the subcommands to convert into a PNG file:
+ baseCommand = baseCommand .. " --export-png="
+ .. tmp .. ".png --export-dpi=300"
+ knownFormat = true
+
+ elseif filetype == "svg" then
+
+ -- Append the subcommands to convert into a SVG file:
+ baseCommand = baseCommand .. " --export-plain-svg=" .. tmp .. ".svg"
+ knownFormat = true
+
+ end
+
+ -- Unfortunately, continuation is only possible, if we know the actual
+ -- format:
+ local imgData = nil
+ if knownFormat then
+
+ -- We know the desired format. Thus, execute Inkscape:
+ os.execute("\"" .. inkscapePath .. "\"" .. baseCommand)
+
+ -- Try to open the image:
+ local r = io.open(tmp .. "." .. filetype, 'rb')
+
+ -- Read the image, if available:
+ if r then
+ imgData = r:read("*all")
+ r:close()
+ end
+
+ -- Delete the image tmp file:
+ os.remove(outfile)
+ end
+
+ -- Remove the temporary files:
+ os.remove(tmp .. ".tex")
+ os.remove(tmp .. ".pdf")
+ os.remove(tmp .. ".log")
+ os.remove(tmp .. ".aux")
+
+ return imgData
+end
+
+-- Run Python to generate an image:
+local function py2image(code, filetype)
+
+ -- Define the temp files:
+ local outfile = string.format('%s.%s', os.tmpname(), filetype)
+ local pyfile = os.tmpname()
+
+ -- Replace the desired destination's file type in the Python code:
+ local extendedCode = string.gsub(code, "%$FORMAT%$", filetype)
+
+ -- Replace the desired destination's path in the Python code:
+ extendedCode = string.gsub(extendedCode, "%$DESTINATION%$", outfile)
+
+ -- Write the Python code:
+ local f = io.open(pyfile, 'w')
+ f:write(extendedCode)
+ f:close()
+
+ -- Execute Python in the desired environment:
+ local pycmd = pythonPath .. ' ' .. pyfile
+ local command = pythonActivatePath
+ and pythonActivatePath .. ' && ' .. pycmd
+ or pycmd
+ os.execute(command)
+
+ -- Try to open the written image:
+ local r = io.open(outfile, 'rb')
+ local imgData = nil
+
+ -- When the image exist, read it:
+ if r then
+ imgData = r:read("*all")
+ r:close()
+ else
+ io.stderr:write(string.format("File '%s' could not be opened", outfile))
+ end
+
+ -- Delete the tmp files:
+ os.remove(pyfile)
+ os.remove(outfile)
+
+ return imgData
+end
+
+-- Executes each document's code block to find matching code blocks:
+function CodeBlock(block)
+
+ -- Predefine a potential image:
+ local fname = nil
+
+ -- Using a table with all known generators i.e. converters:
+ local converters = {
+ plantuml = plantuml,
+ graphviz = graphviz,
+ tikz = tikz2image,
+ py2image = py2image,
+ }
+
+ -- Check if a converter exists for this block. If not, return the block
+ -- unchanged.
+ local img_converter = converters[block.classes[1]]
+ if not img_converter then
+ return nil
+ end
+
+ -- Call the correct converter which belongs to the used class:
+ local success, img = pcall(img_converter, block.text,
+ filetype, block.attributes["additionalPackages"] or nil)
+
+ -- Was ok?
+ if success and img then
+ -- Hash the figure name and content:
+ fname = pandoc.sha1(img) .. "." .. filetype
+
+ -- Store the data in the media bag:
+ pandoc.mediabag.insert(fname, mimetype, img)
+
+ else
+
+ -- an error occured; img contains the error message
+ io.stderr:write(tostring(img))
+ io.stderr:write('\n')
+
+ end
+
+ -- Case: This code block was an image e.g. PlantUML or dot/Graphviz, etc.:
+ if fname then
+
+ -- Define the default caption:
+ local caption = {}
+ local enableCaption = nil
+
+ -- If the user defines a caption, use it:
+ if block.attributes["caption"] then
+ caption = pandoc.read(block.attributes.caption).blocks[1].content
+
+ -- This is pandoc's current hack to enforce a caption:
+ enableCaption = "fig:"
+ end
+
+ -- Create a new image for the document's structure. Attach the user's
+ -- caption. Also use a hack (fig:) to enforce pandoc to create a
+ -- figure i.e. attach a caption to the image.
+ local imgObj = pandoc.Image(caption, fname, enableCaption)
+
+ -- Now, transfer the attribute "name" from the code block to the new
+ -- image block. It might gets used by the figure numbering lua filter.
+ -- If the figure numbering gets not used, this additional attribute
+ -- gets ignored as well.
+ if block.attributes["name"] then
+ imgObj.attributes["name"] = block.attributes["name"]
+ end
+
+ -- Finally, put the image inside an empty paragraph. By returning the
+ -- resulting paragraph object, the source code block gets replaced by
+ -- the image:
+ return pandoc.Para{ imgObj }
+ end
+end
+
+-- Normally, pandoc will run the function in the built-in order Inlines ->
+-- Blocks -> Meta -> Pandoc. We instead want Meta -> Blocks. Thus, we must
+-- define our custom order:
+return {
+ {Meta = Meta},
+ {CodeBlock = CodeBlock},
+}