├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── _extensions
└── diagram
│ ├── _extension.yaml
│ └── diagram.lua
├── diagram.lua
├── sample.md
└── test
├── expected-asymptote.html
├── expected-cetz.html
├── expected-dot.html
├── expected-mermaid.html
├── expected-no-alt-or-caption.html
├── expected-plantuml.html
├── expected-tikz.html
├── input-asymptote.md
├── input-cetz.md
├── input-dot.md
├── input-mermaid.md
├── input-no-alt-or-caption.md
├── input-plantuml.md
├── input-tikz.md
├── plantuml-quarto.qmd
├── test-asymptote.yaml
├── test-cetz.yaml
├── test-dot.yaml
├── test-mermaid.yaml
├── test-no-alt-or-caption.yaml
├── test-plantuml.yaml
└── test-tikz.yaml
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [Makefile]
14 | indent_style = tab
15 |
16 | [*.lua]
17 | indent_style = space
18 | indent_size = 2
19 | # Code should stay below 80 characters per line.
20 | max_line_length = 80
21 |
22 | [*.md]
23 | # Text with 60 to 66 characters per line is said to be the easiest
24 | # to read.
25 | max_line_length = 66
26 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [tarleb]
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | # Run on all pull requests that change code.
5 | pull_request:
6 | paths-ignore:
7 | - 'README.md'
8 | - LICENSE
9 | - .editorconfig
10 | # Run every time a code change is pushed.
11 | push:
12 | paths-ignore:
13 | - 'README.md'
14 | - LICENSE
15 | - .editorconfig
16 | # Test if things still work each Tuesday morning.
17 | # This way we will catch incompatible pandoc changes in a timely
18 | # manner.
19 | schedule:
20 | # At 4:27am each Tuesday
21 | - cron: '27 4 * * 2'
22 |
23 | jobs:
24 | Asymptote:
25 | runs-on: ubuntu-latest
26 | strategy:
27 | fail-fast: true
28 | matrix:
29 | pandoc:
30 | - latest
31 |
32 | container:
33 | image: pandoc/latex:${{ matrix.pandoc }}-ubuntu
34 |
35 | steps:
36 | - name: Checkout
37 | uses: actions/checkout@v4
38 |
39 | - name: Install dependencies
40 | run: |
41 | apt-get -q --no-allow-insecure-repositories update && \
42 | apt-get install --no-install-recommends --assume-yes \
43 | make inkscape asymptote
44 |
45 | - name: Test
46 | run: 'make test-asymptote'
47 |
48 | GraphViz:
49 | runs-on: ubuntu-latest
50 | strategy:
51 | fail-fast: true
52 | matrix:
53 | pandoc:
54 | - latest
55 |
56 | container:
57 | image: pandoc/core:${{ matrix.pandoc }}-ubuntu
58 |
59 | steps:
60 | - name: Checkout
61 | uses: actions/checkout@v4
62 |
63 | - name: Install dependencies
64 | run: |
65 | apt-get -q --no-allow-insecure-repositories update && \
66 | apt-get install --no-install-recommends --assume-yes \
67 | make graphviz
68 |
69 | - name: Test
70 | run: 'make test-dot test-no-alt-or-caption'
71 |
72 | Mermaid:
73 | runs-on: ubuntu-latest
74 | strategy:
75 | fail-fast: true
76 | matrix:
77 | pandoc:
78 | - latest
79 |
80 | container:
81 | image: pandoc/core:${{ matrix.pandoc }}
82 |
83 | env:
84 | MERMAID_BIN: /usr/local/bin/mmdc-test
85 | PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium-browser
86 |
87 | steps:
88 | - name: Checkout
89 | uses: actions/checkout@v4
90 |
91 | - name: Install dependencies
92 | ## We need a little hack to get puppeteer working in the container.
93 | run: |
94 | apk update && apk add chromium chromium-chromedriver make npm
95 | npm install -g @mermaid-js/mermaid-cli
96 | printf '{"args":["--no-sandbox","--disable-setuid-sandbox", "--disable-gpu"]}' > \
97 | /etc/puppeteer-conf.json
98 | printf '#!/bin/sh\nmmdc -p /etc/puppeteer-conf.json $@' > $MERMAID_BIN
99 | chmod +x $MERMAID_BIN
100 |
101 | - name: Test
102 | run: 'make test-mermaid'
103 |
104 | PlantUML:
105 | runs-on: ubuntu-latest
106 | strategy:
107 | fail-fast: true
108 | matrix:
109 | pandoc:
110 | - edge
111 | - latest
112 | # The oldest version that's guaranteed to be supported
113 | - '3.0'
114 |
115 | container:
116 | image: pandoc/core:${{ matrix.pandoc }}-ubuntu
117 |
118 | steps:
119 | - name: Checkout
120 | uses: actions/checkout@v4
121 |
122 | - name: Install dependencies
123 | run: |
124 | apt-get -q --no-allow-insecure-repositories update && \
125 | apt-get install --no-install-recommends --assume-yes \
126 | make plantuml
127 |
128 | - name: Test
129 | run: make test-plantuml
130 |
131 | TikZ:
132 | runs-on: ubuntu-latest
133 | strategy:
134 | fail-fast: true
135 | matrix:
136 | pandoc:
137 | - latest
138 |
139 | container:
140 | image: pandoc/latex:${{ matrix.pandoc }}-ubuntu
141 |
142 | steps:
143 | - name: Checkout
144 | uses: actions/checkout@v4
145 |
146 | - name: Install dependencies
147 | run: |
148 | tlmgr install pgf standalone
149 | apt-get -q --no-allow-insecure-repositories update && \
150 | apt-get install --no-install-recommends --assume-yes \
151 | make inkscape
152 |
153 | - name: Test
154 | run: 'make test-tikz'
155 |
156 | CeTZ:
157 | runs-on: ubuntu-latest
158 | strategy:
159 | fail-fast: true
160 | container:
161 | image: ghcr.io/quarto-dev/quarto:latest
162 |
163 | steps:
164 | - name: Checkout
165 | uses: actions/checkout@v4
166 |
167 | - name: Install dependencies
168 | run: |
169 | apt-get -q --no-allow-insecure-repositories update && \
170 | apt-get install --no-install-recommends --assume-yes \
171 | ca-certificates make
172 |
173 | - name: Render
174 | run: make PANDOC=/opt/quarto/bin/tools/x86_64/pandoc test-cetz
175 |
176 | Quarto:
177 | runs-on: ubuntu-latest
178 | strategy:
179 | fail-fast: true
180 | container:
181 | image: ghcr.io/quarto-dev/quarto:latest
182 |
183 | steps:
184 | - name: Checkout
185 | uses: actions/checkout@v4
186 |
187 | - name: Install dependencies
188 | run: |
189 | apt-get -q --no-allow-insecure-repositories update && \
190 | apt-get install --no-install-recommends --assume-yes \
191 | make plantuml
192 |
193 | # Quarto rendering should complete without failure, and the
194 | # resulting HTML page should contain an image.
195 | - name: Render
196 | run: quarto render test/plantuml-quarto.qmd
197 |
198 | - name: Check for image
199 | run: grep -q ' [!IMPORTANT]
20 | > This filter makes the generated images available to pandoc, but
21 | > *does not* write image files by itself. Use pandoc's
22 | > `--extract-media` to write the generated images to disk. Or,
23 | > when producing HTML, use `--embed-resources` to incorporate the
24 | > images in the output file via `data` URIs.
25 |
26 | ### Plain pandoc
27 |
28 | Pass the filter to pandoc via the `--lua-filter` (or `-L`) command
29 | line option.
30 |
31 | pandoc --lua-filter diagram.lua ...
32 |
33 | ### Quarto
34 |
35 | Users of Quarto can install this filter as an extension with
36 |
37 | quarto install extension pandoc-ext/diagram
38 |
39 | and use it by adding `diagram` to the `filters` entry in their
40 | YAML header.
41 |
42 | ``` yaml
43 | ---
44 | filters:
45 | - diagram
46 | ---
47 | ```
48 |
49 | #### Notes on usage with Quarto
50 |
51 | Quarto comes with its own system for diagram generation that can
52 | be used for a variety of diagrams. Especially Mermaid diagram
53 | generation is much faster with Quarto's built-in diagram handling.
54 |
55 | Due to the way in which Quarto handles code blocks, *do not* add
56 | `filename` attributes to code block attribute lists.
57 |
58 | `````` markdown
59 | ``` {.tikz filename="my-graph"}
60 | % DON'T use the filename attribute on code blocks
61 | ...
62 | ``````
63 |
64 | Instead, use the "comment-pipe" syntax to define the graphic's
65 | file name.
66 |
67 | `````` markdown
68 | ``` tikz
69 | %%| filename: my-graph
70 | % This should work ok.
71 | ...
72 | ```
73 | ``````
74 |
75 | ### R Markdown
76 |
77 | Use `pandoc_args` to invoke the filter. See the [R Markdown
78 | Cookbook](https://bookdown.org/yihui/rmarkdown-cookbook/lua-filters.html)
79 | for details.
80 |
81 | ``` yaml
82 | ---
83 | output:
84 | word_document:
85 | pandoc_args: ['--lua-filter=diagram.lua']
86 | ---
87 | ```
88 |
89 | Diagram types
90 | -------------
91 |
92 | The table below lists the supported diagram drawing systems, the
93 | class that must be used for the system, and the main executable
94 | that the filter calls to generate an image from the code. The
95 | *environment variables* column lists the names of env variables
96 | that can be used to specify a specific executable.
97 |
98 | | System | code block class | executable | env variable |
99 | |-------------|-------------------|------------|-----------------|
100 | | [Asymptote] | `asymptote` | `asy` | `ASYMPTOTE_BIN` |
101 | | [GraphViz] | `dot` | `dot` | `DOT_BIN` |
102 | | [Mermaid] | `mermaid` | `mmdc` | `MERMAID_BIN` |
103 | | [PlantUML] | `plantuml` | `plantuml` | `PLANTUML_BIN` |
104 | | [Ti*k*Z] | `tikz` | `pdflatex` | `PDFLATEX_BIN` |
105 | | [cetz] | `cetz` | `typst` | `TYPST_BIN` |
106 |
107 | ### Other diagram engines
108 |
109 | The filter can be extended with local packages; see
110 | [Configuration](#configuration) below.
111 |
112 | [Asymptote]: https://asymptote.sourceforge.io/
113 | [GraphViz]: https://www.graphviz.org/
114 | [Mermaid]: https://mermaid.js.org/
115 | [PlantUML]: https://plantuml.com/
116 | [Ti*k*Z]: https://github.com/pgf-tikz/pgf
117 | [Cetz]: https://github.com/cetz-package/cetz
118 |
119 | Figure options
120 | --------------
121 |
122 | Options can be given using the syntax pioneered by [Quarto]:
123 |
124 | ````
125 | ``` {.dot}
126 | //| label: fig-boring
127 | //| fig-cap: "A boring Graphviz graph."
128 | digraph boring {
129 | A -> B;
130 | }
131 | ```
132 | ````
133 |
134 | [Quarto]: https://quarto.org/
135 |
136 | Configuration
137 | -------------
138 |
139 | The filter can be configured with the `diagram` metadata entry.
140 |
141 | Currently supported options:
142 |
143 | - `cache`: controls whether the images are cached. If the cache is
144 | enabled, then the images are recreated only when their code
145 | changes. This option is *disabled* by default.
146 |
147 | - `cache-dir`: Sets the directory in which the images are cached.
148 | The default is to use the `pandoc-diagram-filter` subdir of the
149 | a common caching location. This will be, in the order of
150 | preference, the value of the `XDG_CACHE_HOME` environment
151 | variable if it is set, or alternatively `%USERPROFILE%\.cache` on
152 | Windows and `$HOME/.cache` on all other platforms.
153 |
154 | Caching is disabled if none of the environment variables
155 | mentioned above have been defined.
156 |
157 | - `engine`: options for specific engines, e.g. `plantuml` or
158 | `mermaid`. The options must be nested below the engine name.
159 | Allowed settings are either `true` or `false` to enable or
160 | disable the engine, respectively, or a map of options.
161 | The available settings are:
162 |
163 | + `mime-type`: the output MIME type that should be produced with
164 | this engine. This can be used to choose a specific type, or to
165 | disable certain output formats. For example, the following
166 | disables support for PDF output in PlantUML, which can be
167 | useful when the necessary libraries are unavailable on a
168 | system:
169 |
170 | ``` yaml
171 | diagram:
172 | engine:
173 | plantuml:
174 | mime-type:
175 | application/pdf: false
176 | ```
177 |
178 | + `line_comment_start`: the character sequence that starts a
179 | line comment; unset or change this to disable or modify the
180 | syntax of user options in the diagram code.
181 |
182 | + `execpath`: the path to the engine's executable. Use this to
183 | override the default executable name listed in the table
184 | above.
185 |
186 | Use a list to pass additional arguments to the executable.
187 | E.g., `execpath: ['xelatex' '-halt-on-error']` will use
188 | `xelatex` as the executable and pass `-halt-on-error` as the
189 | first argument.
190 |
191 | + `package`: if this option is set then the filter will try to
192 | `require` a Lua package with the given name. If the operation
193 | is successful, then the result will be used as the compiler
194 | for that diagram type.
195 |
196 | + Any other option is passed through to the engine. See the
197 | engine-specific settings below.
198 |
199 | ### Engine-specific options
200 |
201 | Some engines accept additional options. These options can either
202 | be passed globally as part of the respective `engine` entry, or
203 | locally by adding `opt-NAME` as an attribute to the diagram code
204 | block. Global options always override local options for security
205 | reasons.
206 |
207 | #### Ti*k*Z
208 |
209 | The Ti*k*Z engine accepts the `header-includes` and
210 | `additional-packages` options. Both options are added to the
211 | intermediary TeX file that is used to produce the output file. The
212 | options differ only in how string values are handled, with bare
213 | strings in `header-includes` being escaped and those in
214 | `additional-packages` being treated as TeX code.
215 |
216 | While mentioned above, it should be highlighted that the
217 | `execpath` option can be used to select a specific LaTeX engine.
218 | The default is `pdflatex`.
219 |
220 | Example:
221 |
222 | ``` yaml
223 | ---
224 | diagram:
225 | engine:
226 | tikz:
227 | execpath: lualatex
228 | header-includes:
229 | - '\usepackage{adjustbox}'
230 | - '\usetikzlibrary{arrows, shapes}'
231 | ---
232 | ```
233 |
234 | Security
235 | --------
236 |
237 | This filter **should not** be used with **untrusted documents**,
238 | ***unless*** local configs prevent the setting of filter options
239 | in the metadata: An attacker that can set the execpath for an
240 | engine can execute any binary on the system with the user's
241 | permissions. It is hence recommended to review any document before
242 | using it with this filter to avoid malicious and misuse of the
243 | filter.
244 |
245 | The security is improved considerably if the `diagram` metadata
246 | field is unset or set to a predefined value before this filter is
247 | called, e.g., via another filter or a defaults file.
248 |
249 | Here is an example defaults file that configures the filter such
250 | that the configs cannot be overwritten by the document.
251 |
252 | ``` yaml
253 | # file: diagram-filter.yaml
254 | filters: ['diagram.lua']
255 | metadata:
256 | engine:
257 | # enable dot/GraphViz and PlantUML with default options
258 | dot: true
259 | plantuml: true
260 |
261 | # disable processing of asymptote and Mermaid diagrams
262 | asymptote: false
263 | mermaid: false
264 |
265 | # Use LuaLaTeX to compile TikZ, define headers
266 | tikz:
267 | execpath: lualatex
268 | additional-packages: |
269 | \usepackage{adjustbox}
270 | \usetikzlibrary{arrows, shapes}
271 | ```
272 |
273 | Usage:
274 |
275 | pandoc -d diagram-filter ...
276 |
--------------------------------------------------------------------------------
/_extensions/diagram/_extension.yaml:
--------------------------------------------------------------------------------
1 | title: diagram
2 | author: Albert Krewinkel
3 | version: 1.2.0
4 | quarto-required: ">=1.3"
5 | contributes:
6 | filters:
7 | - diagram.lua
8 |
--------------------------------------------------------------------------------
/_extensions/diagram/diagram.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | diagram – create images and figures from code blocks.
3 |
4 | See copyright notice in file LICENSE.
5 | ]]
6 | -- The filter uses the Figure AST element, which was added in pandoc 3.
7 | PANDOC_VERSION:must_be_at_least '3.0'
8 |
9 | local version = pandoc.types.Version '1.2.0'
10 |
11 | -- Report Lua warnings to stderr if the `warn` function is not plugged into
12 | -- pandoc's logging system.
13 | if not warn then
14 | -- fallback
15 | warn = function(...) io.stderr:write(table.concat({ ... })) end
16 | elseif PANDOC_VERSION < '3.1.4' then
17 | -- starting with pandoc 3.1.4, warnings are reported to pandoc's logging
18 | -- system, so no need to print warnings to stderr.
19 | warn '@on'
20 | end
21 |
22 | local io = require 'io'
23 | local pandoc = require 'pandoc'
24 | local system = require 'pandoc.system'
25 | local utils = require 'pandoc.utils'
26 | local List = require 'pandoc.List'
27 | local stringify = utils.stringify
28 | local with_temporary_directory = system.with_temporary_directory
29 | local with_working_directory = system.with_working_directory
30 |
31 | --- Returns a filter-specific directory in which cache files can be
32 | --- stored, or nil if no such directory is available.
33 | local function cachedir ()
34 | local cache_home = os.getenv 'XDG_CACHE_HOME'
35 | if not cache_home or cache_home == '' then
36 | local user_home = system.os == 'windows'
37 | and os.getenv 'USERPROFILE'
38 | or os.getenv 'HOME'
39 |
40 | if not user_home or user_home == '' then
41 | return nil
42 | end
43 | cache_home = pandoc.path.join{user_home, '.cache'} or nil
44 | end
45 |
46 | -- Create filter cache directory
47 | return pandoc.path.join{cache_home, 'pandoc-diagram-filter'}
48 | end
49 |
50 | --- Path holding the image cache, or `nil` if the cache is not used.
51 | local image_cache = nil
52 |
53 | local mimetype_for_extension = {
54 | jpeg = 'image/jpeg',
55 | jpg = 'image/jpeg',
56 | pdf = 'application/pdf',
57 | png = 'image/png',
58 | svg = 'image/svg+xml',
59 | }
60 |
61 | local extension_for_mimetype = {
62 | ['application/pdf'] = 'pdf',
63 | ['image/jpeg'] = 'jpg',
64 | ['image/png'] = 'png',
65 | ['image/svg+xml'] = 'svg',
66 | }
67 |
68 | --- Converts a list of format specifiers to a set of MIME types.
69 | local function mime_types_set (tbl)
70 | local set = {}
71 | local mime_type
72 | for _, image_format_spec in ipairs(tbl) do
73 | mime_type = mimetype_for_extension[image_format_spec] or image_format_spec
74 | set[mime_type] = true
75 | end
76 | return set
77 | end
78 |
79 | --- Reads the contents of a file.
80 | local function read_file (filepath)
81 | local fh = io.open(filepath, 'rb')
82 | local contents = fh:read('a')
83 | fh:close()
84 | return contents
85 | end
86 |
87 | --- Writes the contents into a file at the given path.
88 | local function write_file (filepath, content)
89 | local fh = io.open(filepath, 'wb')
90 | fh:write(content)
91 | fh:close()
92 | end
93 |
94 | --- Like `pandoc.pipe`, but allows "multi word" paths:
95 | -- Supplying a list as the first argument will use the first element as
96 | -- the executable path and prepend the remaining elements to the list of
97 | -- arguments.
98 | local function pipe (command, args, input)
99 | local cmd
100 | if pandoc.utils.type(command) == 'List' then
101 | command = command:map(stringify)
102 | cmd = command:remove(1)
103 | args = command .. args
104 | else
105 | cmd = stringify(command)
106 | end
107 | return pandoc.pipe(cmd, args, input)
108 | end
109 |
110 |
111 | --
112 | -- Diagram Engines
113 | --
114 |
115 | -- PlantUML engine; assumes that there's a `plantuml` binary.
116 | local plantuml = {
117 | line_comment_start = [[']],
118 | mime_types = mime_types_set{'pdf', 'png', 'svg'},
119 | compile = function (self, puml)
120 | local mime_type = self.mime_type or 'image/svg+xml'
121 | -- PlantUML format identifiers correspond to common file extensions.
122 | local format = extension_for_mimetype[mime_type]
123 | if not format then
124 | format, mime_type = 'svg', 'image/svg+xml'
125 | end
126 | local args = {'-t' .. format, "-pipe", "-charset", "UTF8"}
127 | return pipe(self.execpath or 'plantuml', args, puml), mime_type
128 | end,
129 | }
130 |
131 | --- GraphViz engine for the dot language
132 | local graphviz = {
133 | line_comment_start = '//',
134 | mime_types = mime_types_set{'jpg', 'pdf', 'png', 'svg'},
135 | mime_type = 'image/svg+xml',
136 | compile = function (self, code)
137 | local mime_type = self.mime_type
138 | -- GraphViz format identifiers correspond to common file extensions.
139 | local format = extension_for_mimetype[mime_type]
140 | if not format then
141 | format, mime_type = 'svg', 'image/svg+xml'
142 | end
143 | return pipe(self.execpath or 'dot', {"-T"..format}, code), mime_type
144 | end,
145 | }
146 |
147 | --- Mermaid engine
148 | local mermaid = {
149 | line_comment_start = '%%',
150 | mime_types = mime_types_set{'pdf', 'png', 'svg'},
151 | compile = function (self, code)
152 | local mime_type = self.mime_type or 'image/svg+xml'
153 | local file_extension = extension_for_mimetype[mime_type]
154 | return with_temporary_directory("diagram", function (tmpdir)
155 | return with_working_directory(tmpdir, function ()
156 | local infile = 'diagram.mmd'
157 | local outfile = 'diagram.' .. file_extension
158 | write_file(infile, code)
159 | pipe(
160 | self.execpath or 'mmdc',
161 | {"--pdfFit", "--input", infile, "--output", outfile},
162 | ''
163 | )
164 | return read_file(outfile), mime_type
165 | end)
166 | end)
167 | end,
168 | }
169 |
170 | --- TikZ
171 | --
172 |
173 | --- LaTeX template used to compile TikZ images.
174 | local tikz_template = pandoc.template.compile [[
175 | \documentclass{standalone}
176 | \usepackage{tikz}
177 | $for(header-includes)$
178 | $it$
179 | $endfor$
180 | $additional-packages$
181 | \begin{document}
182 | $body$
183 | \end{document}
184 | ]]
185 |
186 | --- The TikZ engine uses pdflatex to compile TikZ code to an image
187 | local tikz = {
188 | line_comment_start = '%%',
189 |
190 | mime_types = {
191 | ['application/pdf'] = true,
192 | },
193 |
194 | --- Compile LaTeX with TikZ code to an image
195 | compile = function (self, src, user_opts)
196 | return with_temporary_directory("tikz", function (tmpdir)
197 | return with_working_directory(tmpdir, function ()
198 | -- Define file names:
199 | local file_template = "%s/tikz-image.%s"
200 | local tikz_file = file_template:format(tmpdir, "tex")
201 | local pdf_file = file_template:format(tmpdir, "pdf")
202 |
203 | -- Treat string values as raw LaTeX
204 | local meta = {
205 | ['header-includes'] = user_opts['header-includes'],
206 | ['additional-packages'] = {pandoc.RawInline(
207 | 'latex',
208 | stringify(user_opts['additional-packages'] or '')
209 | )},
210 | }
211 | local tex_code = pandoc.write(
212 | pandoc.Pandoc({pandoc.RawBlock('latex', src)}, meta),
213 | 'latex',
214 | {template = tikz_template}
215 | )
216 | write_file(tikz_file, tex_code)
217 |
218 | -- Execute the LaTeX compiler:
219 | local success, result = pcall(
220 | pipe,
221 | self.execpath or 'pdflatex',
222 | { '-interaction=nonstopmode', '-output-directory', tmpdir, tikz_file },
223 | ''
224 | )
225 | if not success then
226 | warn(string.format(
227 | "The call\n%s\nfailed with error code %s. Output:\n%s",
228 | result.command,
229 | result.error_code,
230 | result.output
231 | ))
232 | end
233 | return read_file(pdf_file), 'application/pdf'
234 | end)
235 | end)
236 | end
237 | }
238 |
239 | --- Asymptote diagram engine
240 | local asymptote = {
241 | line_comment_start = '%%',
242 | mime_types = {
243 | ['application/pdf'] = true,
244 | },
245 | compile = function (self, code)
246 | return with_temporary_directory("asymptote", function(tmpdir)
247 | return with_working_directory(tmpdir, function ()
248 | local pdf_file = "pandoc_diagram.pdf"
249 | local args = {'-tex', 'pdflatex', "-o", "pandoc_diagram", '-'}
250 | pipe(self.execpath or 'asy', args, code)
251 | return read_file(pdf_file), 'application/pdf'
252 | end)
253 | end)
254 | end,
255 | }
256 |
257 | --- Cetz diagram engine
258 | local cetz = {
259 | line_comment_start = '%%',
260 | mime_types = mime_types_set{'jpg', 'pdf', 'png', 'svg'},
261 | mime_type = 'image/svg+xml',
262 | compile = function (self, code)
263 | local mime_type = self.mime_type
264 | local format = extension_for_mimetype[mime_type]
265 | if not format then
266 | format, mime_type = 'svg', 'image/svg+xml'
267 | end
268 | local preamble = [[
269 | #import "@preview/cetz:0.3.4"
270 | #set page(width: auto, height: auto, margin: .5cm)
271 | ]]
272 |
273 | local typst_code = preamble .. code
274 |
275 | return with_temporary_directory("diagram", function (tmpdir)
276 | return with_working_directory(tmpdir, function ()
277 | local outfile = 'diagram.' .. format
278 | local execpath = self.execpath
279 | if not execpath and quarto and quarto.version >= '1.4' then
280 | -- fall back to the Typst exec shipped with Quarto.
281 | execpath = List{'quarto', 'typst'}
282 | end
283 | pipe(
284 | execpath or 'typst',
285 | {"compile", "-f", format, "-", outfile},
286 | typst_code
287 | )
288 | return read_file(outfile), mime_type
289 | end)
290 | end)
291 | end,
292 | }
293 |
294 | local default_engines = {
295 | asymptote = asymptote,
296 | dot = graphviz,
297 | mermaid = mermaid,
298 | plantuml = plantuml,
299 | tikz = tikz,
300 | cetz = cetz,
301 | }
302 |
303 | --
304 | -- Configuration
305 | --
306 |
307 | --- Options for the output format of the given name.
308 | local function format_options (name)
309 | local pdf2svg = name ~= 'latex' and name ~= 'context'
310 | local is_office_format = name == 'docx' or name == 'odt'
311 | -- Office formats seem to work better with PNG than with SVG.
312 | local preferred_mime_types = is_office_format
313 | and pandoc.List{'image/png', 'application/pdf'}
314 | or pandoc.List{'application/pdf', 'image/png'}
315 | -- Prefer SVG for non-PDF output formats, except for Office formats
316 | if is_office_format then
317 | preferred_mime_types:insert('image/svg+xml')
318 | elseif pdf2svg then
319 | preferred_mime_types:insert(1, 'image/svg+xml')
320 | end
321 | return {
322 | name = name,
323 | pdf2svg = pdf2svg,
324 | preferred_mime_types = preferred_mime_types,
325 | best_mime_type = function (self, supported_mime_types, requested)
326 | return self.preferred_mime_types:find_if(function (preferred)
327 | return supported_mime_types[preferred] and
328 | (not requested or
329 | (pandoc.utils.type(requested) == 'List' and
330 | requested:includes(preferred)) or
331 | (pandoc.utils.type(requested) == 'table' and
332 | requested[preferred]) or
333 |
334 | -- Assume string, Inlines, and Blocks values specify the only
335 | -- acceptable MIME type.
336 | stringify(requested) == preferred)
337 | end)
338 | end
339 | }
340 | end
341 |
342 | --- Returns a configured diagram engine.
343 | local function get_engine (name, engopts, format)
344 | local engine = default_engines[name] or
345 | select(2, pcall(require, stringify(engopts.package)))
346 |
347 | -- Sanity check
348 | if not engine then
349 | warn(PANDOC_SCRIPT_FILE, ": No such engine '", name, "'.")
350 | return nil
351 | elseif engopts == false then
352 | -- engine is disabled
353 | return nil
354 | elseif engopts == true then
355 | -- use default options
356 | return engine
357 | end
358 |
359 | local execpath = engopts.execpath or os.getenv(name:upper() .. '_BIN')
360 |
361 | local mime_type = format:best_mime_type(
362 | engine.mime_types,
363 | engopts['mime-type'] or engopts['mime-types']
364 | )
365 | if not mime_type then
366 | warn(PANDOC_SCRIPT_FILE, ": Cannot use ", name, " with ", format.name)
367 | return nil
368 | end
369 |
370 | return {
371 | execpath = execpath,
372 | compile = engine.compile,
373 | line_comment_start = engine.line_comment_start,
374 | mime_type = mime_type,
375 | opt = engopts or {},
376 | }
377 | end
378 |
379 | --- Returns the diagram engine configs.
380 | local function configure (meta, format_name)
381 | local conf = meta.diagram or {}
382 | local format = format_options(format_name)
383 | meta.diagram = nil
384 |
385 | -- cache for image files
386 | if conf.cache then
387 | image_cache = conf['cache-dir']
388 | and stringify(conf['cache-dir'])
389 | or cachedir()
390 | pandoc.system.make_directory(image_cache, true)
391 | end
392 |
393 | -- engine configs
394 | local engine = {}
395 | for name, engopts in pairs(conf.engine or default_engines) do
396 | engine[name] = get_engine(name, engopts, format)
397 | end
398 |
399 | return {
400 | engine = engine,
401 | format = format,
402 | cache = image_cache and true,
403 | image_cache = image_cache,
404 | }
405 | end
406 |
407 | --
408 | -- Format conversion
409 | --
410 |
411 | --- Converts a PDF to SVG.
412 | local pdf2svg = function (imgdata)
413 | -- Using `os.tmpname()` instead of a hash would be slightly cleaner, but the
414 | -- function causes problems on Windows (and wasm). See, e.g.,
415 | -- https://github.com/pandoc-ext/diagram/issues/49
416 | local pdf_file = 'diagram-' .. pandoc.utils.sha1(imgdata) .. '.pdf'
417 | write_file(pdf_file, imgdata)
418 | local args = {
419 | '--export-type=svg',
420 | '--export-plain-svg',
421 | '--export-filename=-',
422 | pdf_file
423 | }
424 | return pandoc.pipe('inkscape', args, ''), os.remove(pdf_file)
425 | end
426 |
427 | local function properties_from_code (code, comment_start)
428 | local props = {}
429 | local pattern = comment_start:gsub('%p', '%%%1') .. '| ' ..
430 | '([-_%w]+): ([^\n]*)\n'
431 | for key, value in code:gmatch(pattern) do
432 | if key == 'fig-cap' then
433 | props['caption'] = value
434 | else
435 | props[key] = value
436 | end
437 | end
438 | return props
439 | end
440 |
441 | local function diagram_options (cb, comment_start)
442 | local attribs = comment_start
443 | and properties_from_code(cb.text, comment_start)
444 | or {}
445 | for key, value in pairs(cb.attributes) do
446 | attribs[key] = value
447 | end
448 |
449 | local alt
450 | local caption
451 | local fig_attr = {id = cb.identifier}
452 | local filename
453 | local image_attr = {}
454 | local user_opt = {}
455 |
456 | for attr_name, value in pairs(attribs) do
457 | if attr_name == 'alt' then
458 | alt = value
459 | elseif attr_name == 'caption' then
460 | -- Read caption attribute as Markdown
461 | caption = attribs.caption
462 | and pandoc.read(attribs.caption).blocks
463 | or nil
464 | elseif attr_name == 'filename' then
465 | filename = value
466 | elseif attr_name == 'label' then
467 | fig_attr.id = value
468 | elseif attr_name == 'name' then
469 | fig_attr.name = value
470 | else
471 | -- Check for prefixed attributes
472 | local prefix, key = attr_name:match '^(%a+)%-(%a[-%w]*)$'
473 | if prefix == 'fig' then
474 | fig_attr[key] = value
475 | elseif prefix == 'image' or prefix == 'img' then
476 | image_attr[key] = value
477 | elseif prefix == 'opt' then
478 | user_opt[key] = value
479 | else
480 | -- Use as image attribute
481 | image_attr[attr_name] = value
482 | end
483 | end
484 | end
485 |
486 | return {
487 | ['alt'] = alt or
488 | (caption and pandoc.utils.blocks_to_inlines(caption)) or
489 | {},
490 | ['caption'] = caption,
491 | ['fig-attr'] = fig_attr,
492 | ['filename'] = filename,
493 | ['image-attr'] = image_attr,
494 | ['opt'] = user_opt,
495 | }
496 | end
497 |
498 | local function get_cached_image (hash, mime_type)
499 | if not image_cache then
500 | return nil
501 | end
502 | local filename = hash .. '.' .. extension_for_mimetype[mime_type]
503 | local imgpath = pandoc.path.join{image_cache, filename}
504 | local success, imgdata = pcall(read_file, imgpath)
505 | if success then
506 | return imgdata, mime_type
507 | end
508 | return nil
509 | end
510 |
511 | local function cache_image (codeblock, imgdata, mimetype)
512 | -- do nothing if caching is disabled or not possible.
513 | if not image_cache then
514 | return
515 | end
516 | local ext = extension_for_mimetype[mimetype]
517 | local filename = pandoc.sha1(codeblock.text) .. '.' .. ext
518 | local imgpath = pandoc.path.join{image_cache, filename}
519 | write_file(imgpath, imgdata)
520 | end
521 |
522 | -- Executes each document's code block to find matching code blocks:
523 | local function code_to_figure (conf)
524 | return function (block)
525 | -- Check if a converter exists for this block. If not, return the block
526 | -- unchanged.
527 | local diagram_type = block.classes[1]
528 | if not diagram_type then
529 | return nil
530 | end
531 |
532 | local engine = conf.engine[diagram_type]
533 | if not engine then
534 | return nil
535 | end
536 |
537 | -- Unified properties.
538 | local dgr_opt = diagram_options(block, engine.line_comment_start)
539 | for optname, value in pairs(engine.opt or {}) do
540 | dgr_opt.opt[optname] = dgr_opt.opt[optname] or value
541 | end
542 |
543 | local run_pdf2svg = engine.mime_type == 'application/pdf'
544 | and conf.format.pdf2svg
545 |
546 | -- Try to retrieve the image data from the cache.
547 | local imgdata, imgtype
548 | if conf.cache then
549 | imgdata, imgtype = get_cached_image(
550 | pandoc.sha1(block.text),
551 | run_pdf2svg and 'image/svg+xml' or engine.mime_type
552 | )
553 | end
554 |
555 | if not imgdata or not imgtype then
556 | -- No cached image; call the converter
557 | local success
558 | success, imgdata, imgtype =
559 | pcall(engine.compile, engine, block.text, dgr_opt.opt)
560 |
561 | -- Bail if an error occurred; imgdata contains the error message
562 | -- when that happens.
563 | if not success then
564 | warn(PANDOC_SCRIPT_FILE, ': ', tostring(imgdata))
565 | return nil
566 | elseif not imgdata then
567 | warn(PANDOC_SCRIPT_FILE, ': Diagram engine returned no image data.')
568 | return nil
569 | elseif not imgtype then
570 | warn(PANDOC_SCRIPT_FILE, ': Diagram engine did not return a MIME type.')
571 | return nil
572 | end
573 |
574 | -- Convert SVG if necessary.
575 | if imgtype == 'application/pdf' and conf.format.pdf2svg then
576 | imgdata, imgtype = pdf2svg(imgdata), 'image/svg+xml'
577 | end
578 |
579 | -- If we got here, then the transformation went ok and `img` contains
580 | -- the image data.
581 | cache_image(block, imgdata, imgtype)
582 | end
583 |
584 | -- Use the block's filename attribute or create a new name by hashing the
585 | -- image content.
586 | local basename, _extension = pandoc.path.split_extension(
587 | dgr_opt.filename or pandoc.sha1(imgdata)
588 | )
589 | local fname = basename .. '.' .. extension_for_mimetype[imgtype]
590 |
591 | -- Store the data in the media bag:
592 | pandoc.mediabag.insert(fname, imgtype, imgdata)
593 |
594 | -- Create the image object.
595 | local image = pandoc.Image(dgr_opt.alt, fname, "", dgr_opt['image-attr'])
596 |
597 | -- Create a figure if the diagram has a caption; otherwise return
598 | -- just the image.
599 | return dgr_opt.caption and
600 | pandoc.Figure(
601 | pandoc.Plain{image},
602 | dgr_opt.caption,
603 | dgr_opt['fig-attr']
604 | ) or
605 | pandoc.Plain{image}
606 | end
607 | end
608 |
609 | return setmetatable(
610 | {{
611 | Pandoc = function (doc)
612 | local conf = configure(doc.meta, FORMAT)
613 | return doc:walk {
614 | CodeBlock = code_to_figure(conf),
615 | }
616 | end
617 | }},
618 | {
619 | version = version,
620 | }
621 | )
622 |
--------------------------------------------------------------------------------
/diagram.lua:
--------------------------------------------------------------------------------
1 | _extensions/diagram/diagram.lua
--------------------------------------------------------------------------------
/sample.md:
--------------------------------------------------------------------------------
1 | # Diagram Generator Lua Filter
2 |
3 | ## Introduction
4 | This Lua filter is used to create images with or without captions from code
5 | blocks. Currently PlantUML, Graphviz, Ti*k*Z, Asymptote, and Python can be
6 | processed. This document also serves as a test document, which is why the
7 | subsequent test diagrams are integrated in every supported language.
8 |
9 | ## Prerequisites
10 | To be able to use this Lua filter, the respective external tools must be
11 | installed. However, it is sufficient if the tools to be used are installed.
12 | If you only want to use PlantUML, you don't need LaTeX or Python, etc.
13 |
14 | ### PlantUML
15 | To use PlantUML, you must install PlantUML itself. See the
16 | [PlantUML website](http://plantuml.com/) for more details. It should be
17 | noted that PlantUML is a Java program and therefore Java must also
18 | be installed.
19 |
20 | By default, this filter expects the plantuml.jar file to be in the
21 | working directory. Alternatively, the environment variable
22 | `PLANTUML` can be set with a path. If, for example, a specific
23 | PlantUML version is to be used per pandoc document, the
24 | `plantuml_path` meta variable can be set.
25 |
26 | Furthermore, this filter assumes that Java is located in the
27 | system or user path. This means that from any place of the system
28 | the `java` command is understood. Alternatively, the `JAVA_HOME`
29 | environment variable gets used. To use a specific Java version per
30 | pandoc document, use the `java_path` meta variable. Please notice
31 | that `JAVA_HOME` must be set to the java's home directory e.g.
32 | `c:\Program Files\Java\jre1.8.0_201\` whereas `java_path` must be
33 | set to the absolute path of `java.exe` e.g.
34 | `c:\Program Files\Java\jre1.8.0_201\bin\java.exe`.
35 |
36 | Example usage:
37 |
38 | ```{.plantuml caption="This is an image, created by **PlantUML**." width=50%}
39 | @startuml
40 | Alice -> Bob: Authentication Request Bob --> Alice: Authentication Response
41 | Alice -> Bob: Another authentication Request Alice <-- Bob: another Response
42 | @enduml
43 | ```
44 |
45 | ### Graphviz
46 | To use Graphviz you only need to install Graphviz, as you can read
47 | on its [website](http://www.graphviz.org/). There are no other
48 | dependencies.
49 |
50 | This filter assumes that the `dot` command is located in the path
51 | and therefore can be used from any location. Alternatively, you can
52 | set the environment variable `DOT` or use the pandoc's meta variable
53 | `dot_path`.
54 |
55 | Example usage from [the Graphviz
56 | gallery](https://graphviz.gitlab.io/_pages/Gallery/directed/fsm.html):
57 |
58 | ```{.dot caption="This is an image, created by **Graphviz**'s dot."}
59 | digraph finite_state_machine {
60 | rankdir=LR;
61 | node [shape = doublecircle]; LR_0 LR_3 LR_4 LR_8;
62 | node [shape = circle];
63 | LR_0 -> LR_2 [ label = "SS(B)" ];
64 | LR_0 -> LR_1 [ label = "SS(S)" ];
65 | LR_1 -> LR_3 [ label = "S($end)" ];
66 | LR_2 -> LR_6 [ label = "SS(b)" ];
67 | LR_2 -> LR_5 [ label = "SS(a)" ];
68 | LR_2 -> LR_4 [ label = "S(A)" ];
69 | LR_5 -> LR_7 [ label = "S(b)" ];
70 | LR_5 -> LR_5 [ label = "S(a)" ];
71 | LR_6 -> LR_6 [ label = "S(b)" ];
72 | LR_6 -> LR_5 [ label = "S(a)" ];
73 | LR_7 -> LR_8 [ label = "S(b)" ];
74 | LR_7 -> LR_5 [ label = "S(a)" ];
75 | LR_8 -> LR_6 [ label = "S(b)" ];
76 | LR_8 -> LR_5 [ label = "S(a)" ];
77 | }
78 | ```
79 |
80 | ### Ti*k*Z
81 | Ti*k*Z (cf. [Wikipedia](https://en.wikipedia.org/wiki/PGF/TikZ)) is a
82 | description language for graphics of any kind that can be used within
83 | LaTeX (cf. [Wikipedia](https://en.wikipedia.org/wiki/LaTeX)).
84 |
85 | Therefore a LaTeX system must be installed on the system. The Ti*k*Z code is
86 | embedded into a dynamic LaTeX document. This temporary document gets
87 | translated into a PDF document using LaTeX (`pdflatex`). Finally,
88 | Inkscape is used to convert the PDF file to the desired format.
89 |
90 | Note: We are using Inkscape here to use a stable solution for the
91 | convertion. Formerly ImageMagick was used instead. ImageMagick is
92 | not able to convert PDF files. Hence, it uses Ghostscript to do
93 | so, cf. [1](https://stackoverflow.com/a/6599718/2258393).
94 | Unfortunately, Ghostscript behaves unpredictable during Windows and
95 | Linux tests cases, cf. [2](https://stackoverflow.com/questions/21774561/some-pdfs-are-converted-improperly-using-imagemagick),
96 | [3](https://stackoverflow.com/questions/9064706/imagemagic-convert-command-pdf-convertion-with-bad-size-orientation), [4](https://stackoverflow.com/questions/18837093/imagemagic-renders-image-with-black-background),
97 | [5](https://stackoverflow.com/questions/37392798/pdf-to-svg-is-not-perfect),
98 | [6](https://stackoverflow.com/q/10288065/2258393), etc. By using Inkscape,
99 | we need one dependency less and get rid of unexpected Ghostscript issues.
100 |
101 | Due to this more complicated process, the use of Ti*k*Z is also more
102 | complicated overall. The process is error-prone: An insufficiently
103 | configured LaTeX installation or an insufficiently configured
104 | Inkscape installation can lead to errors. Overall, this results in
105 | the following dependencies:
106 |
107 | - Any LaTeX installation. This should be configured so that
108 | missing packages are installed automatically. This filter uses the
109 | `pdflatex` command which is available by the system's path. Alternatively,
110 | you can set the `PDFLATEX` environment variable. In case you have to use
111 | a specific LaTeX version on a pandoc document basis, you might set the
112 | `pdflatex_path` meta variable.
113 |
114 | - An installation of [Inkscape](https://inkscape.org/).
115 | It is assumed that the `inkscape` command is in the path and can be
116 | executed from any location. Alternatively, the environment
117 | variable `INKSCAPE` can be set with a path. If a specific
118 | version per pandoc document is to be used, the `inkscape_path`
119 | meta-variable can be set.
120 |
121 | In order to use additional LaTeX packages, use the optional
122 | `additionalPackages` attribute in your document, as in the
123 | example below.
124 |
125 | Example usage from [TikZ
126 | examples](http://www.texample.net/tikz/examples/parallelepiped/) by
127 | [Kjell Magne Fauske](http://www.texample.net/tikz/examples/nav1d/):
128 |
129 | ```{.tikz caption="This is an image, created by **TikZ i.e. LaTeX**."
130 | additionalPackages="\usepackage{adjustbox}"}
131 | \usetikzlibrary{arrows}
132 | \tikzstyle{int}=[draw, fill=blue!20, minimum size=2em]
133 | \tikzstyle{init} = [pin edge={to-,thin,black}]
134 |
135 | \resizebox{16cm}{!}{%
136 | \trimbox{3.5cm 0cm 0cm 0cm}{
137 | \begin{tikzpicture}[node distance=2.5cm,auto,>=latex']
138 | \node [int, pin={[init]above:$v_0$}] (a) {$\frac{1}{s}$};
139 | \node (b) [left of=a,node distance=2cm, coordinate] {a};
140 | \node [int, pin={[init]above:$p_0$}] at (0,0) (c)
141 | [right of=a] {$\frac{1}{s}$};
142 | \node [coordinate] (end) [right of=c, node distance=2cm]{};
143 | \path[->] (b) edge node {$a$} (a);
144 | \path[->] (a) edge node {$v$} (c);
145 | \draw[->] (c) edge node {$p$} (end) ;
146 | \end{tikzpicture}
147 | }
148 | }
149 | ```
150 |
151 | ### Python
152 | In order to use Python to generate an diagram, your Python code must store the
153 | final image data in a temporary file with the correct format. In case you use
154 | matplotlib for a diagram, add the following line to do so:
155 |
156 | ```python
157 | plt.savefig("$DESTINATION$", dpi=300, format="$FORMAT$")
158 | ```
159 |
160 | The placeholder `$FORMAT$` gets replace by the necessary format. Most of the
161 | time, this will be `png` or `svg`. The second placeholder, `$DESTINATION$`
162 | gets replaced by the path and file name of the destination. Both placeholders
163 | can be used as many times as you want. Example usage from the [Matplotlib
164 | examples](https://matplotlib.org/gallery/lines_bars_and_markers/cohere.html#sphx-glr-gallery-lines-bars-and-markers-cohere-py):
165 |
166 | ```{.py2image caption="This is an image, created by **Python**."}
167 | import matplotlib
168 | matplotlib.use('Agg')
169 |
170 | import sys
171 | import numpy as np
172 | import matplotlib.pyplot as plt
173 |
174 | # Fixing random state for reproducibility
175 | np.random.seed(19680801)
176 |
177 | dt = 0.01
178 | t = np.arange(0, 30, dt)
179 | nse1 = np.random.randn(len(t)) # white noise 1
180 | nse2 = np.random.randn(len(t)) # white noise 2
181 |
182 | # Two signals with a coherent part at 10Hz and a random part
183 | s1 = np.sin(2 * np.pi * 10 * t) + nse1
184 | s2 = np.sin(2 * np.pi * 10 * t) + nse2
185 |
186 | fig, axs = plt.subplots(2, 1)
187 | axs[0].plot(t, s1, t, s2)
188 | axs[0].set_xlim(0, 2)
189 | axs[0].set_xlabel('time')
190 | axs[0].set_ylabel('s1 and s2')
191 | axs[0].grid(True)
192 |
193 | cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt)
194 | axs[1].set_ylabel('coherence')
195 |
196 | fig.tight_layout()
197 | plt.savefig("$DESTINATION$", dpi=300, format="$FORMAT$")
198 | ```
199 |
200 | Precondition to use Python is a Python environment which contains all
201 | necessary libraries you want to use. To use, for example, the standard
202 | [Anaconda Python](https://www.anaconda.com/distribution/) environment
203 | on a Microsoft Windows system ...
204 |
205 | - set the environment variable `PYTHON` or the meta key `pythonPath`
206 | to `c:\ProgramData\Anaconda3\python.exe`
207 |
208 | - set the environment variable `PYTHON_ACTIVATE` or the meta
209 | key `activate_python_path` to `c:\ProgramData\Anaconda3\Scripts\activate.bat`.
210 |
211 | Pandoc will activate this Python environment and starts Python with your code.
212 |
213 | ## Asymptote
214 | [Asymptote](https://asymptote.sourceforge.io/) is a graphics
215 | language inspired by Metapost. To use Asymptote, you will need to
216 | install the software itself, a TeX distribution such as
217 | [TeX Live](https://www.tug.org/texlive/), and
218 | [dvisvgm](https://dvisvgm.de/), which may be included in the TeX
219 | distribution.
220 |
221 | If png output is required (such as for the `docx`, `pptx` and `rtf`
222 | output formats) Inkscape must be installed. See the Ti*k*Z section
223 | for details.
224 |
225 | Ensure that the Asymptote `asy` binary is in the path, or point
226 | the environment variable `ASYMPTOTE` or the metadata variable
227 | `asymptotePath` to the full path name. Asymptote calls the various
228 | TeX utilities and dvipdfm, so you will need to configure Asymptote
229 | so that it finds them.
230 |
231 | ```{.asymptote caption="This is an image, created by **Asymptote**."}
232 | size(5cm);
233 | include graph;
234 |
235 | pair circumcenter(pair A, pair B, pair C)
236 | {
237 | pair P, Q, R, S;
238 | P = (A+B)/2;
239 | Q = (B+C)/2;
240 | R = rotate(90, P) * A;
241 | S = rotate(90, Q) * B;
242 | return extension(P, R, Q, S);
243 | }
244 |
245 | pair incenter(pair A, pair B, pair C)
246 | {
247 | real a = abs(angle(C-A)-angle(B-A)),
248 | b = abs(angle(C-B)-angle(A-B)),
249 | c = abs(angle(A-C)-angle(B-C));
250 | return (sin(a)*A + sin(b)*B + sin(c)*C) / (sin(a)+sin(b)+sin(c));
251 | }
252 |
253 | real dist_A_BC(pair A, pair B, pair C)
254 | {
255 | real det = cross(B-A, C-A);
256 | return abs(det/abs(B-C));
257 | }
258 |
259 | pair A = (0, 0), B = (5, 0), C = (3.5, 4),
260 | O = circumcenter(A, B, C),
261 | I = incenter(A, B, C);
262 | dot(A); dot(B); dot(C); dot(O, blue); dot(I, magenta);
263 | draw(A--B--C--cycle, linewidth(2));
264 | draw(Circle(O, abs(A-O)), blue+linewidth(1.5));
265 | draw(Circle(I, dist_A_BC(I, A, B)), magenta+linewidth(1.5));
266 | label("$A$", A, SW);
267 | label("$B$", B, SE);
268 | label("$C$", C, NE);
269 | label("$O$", O, W);
270 | label("$I$", I, E);
271 | ```
272 |
273 | ## How to run pandoc
274 | This section will show, how to call Pandoc in order to use this filter with
275 | meta keys. The following command assume, that the filters are stored in the
276 | subdirectory `filters`. Further, this is a example for a Microsoft Windows
277 | system.
278 |
279 | Command to use PlantUML (a single line):
280 |
281 | ```
282 | pandoc.exe README.md -f markdown -t docx --self-contained --standalone --lua-filter=filters\diagram-generator.lua --metadata=plantumlPath:"c:\ProgramData\chocolatey\lib\plantuml\tools\plantuml.jar" --metadata=javaPath:"c:\Program Files\Java\jre1.8.0_201\bin\java.exe" -o README.docx
283 | ```
284 |
285 | All available environment variables:
286 |
287 | - `PLANTUML` e.g. `c:\ProgramData\chocolatey\lib\plantuml\tools\plantuml.jar`; Default: `plantuml.jar`
288 | - `INKSCAPE` e.g. `c:\Program Files\Inkscape\inkscape.exe`; Default: `inkscape`
289 | - `PYTHON` e.g. `c:\ProgramData\Anaconda3\python.exe`; Default: n/a
290 | - `PYTHON_ACTIVATE` e.g. `c:\ProgramData\Anaconda3\Scripts\activate.bat`; Default: n/a
291 | - `JAVA_HOME` e.g. `c:\Program Files\Java\jre1.8.0_201`; Default: n/a
292 | - `DOT` e.g. `c:\ProgramData\chocolatey\bin\dot.exe`; Default: `dot`
293 | - `PDFLATEX` e.g. `c:\Program Files\MiKTeX 2.9\miktex\bin\x64\pdflatex.exe`; Default: `pdflatex`
294 | - `ASYMPTOTE` e.g. `c:\Program Files\Asymptote\asy`; Default: `asy`
295 |
296 | All available meta keys:
297 |
298 | - `plantuml_path`
299 | - `inkscape_path`
300 | - `python_path`
301 | - `activate_python_path`
302 | - `java_path`
303 | - `dot_path`
304 | - `pdflatex_path`
305 | - `asymptote_path`
306 |
--------------------------------------------------------------------------------
/test/expected-asymptote.html:
--------------------------------------------------------------------------------
1 |
The image code 4 | comes from the cetz docs licensed under LGPLv3.
6 |Example usage from the 4 | Graphviz gallery:
5 |Mermaid is a JavaScript-based diagramming and charting tool.
3 |This simple graph has neither alt text nor a caption. It should be 2 | converted to a simple image without any description.
3 |Example usage from TikZ 4 | examples by Kjell Magne 6 | Fauske:
7 |Diagram showing how the delta-graph relates to the other graphs. Note 14 | that this diagram does not have a caption, so it will be rendered as a 15 | plain image instead of a figure.
16 |