Skip to content

Commit 5a8f24a

Browse files
authored
Improve diagram rendering. (#232)
- Add support for inline or included plantuml diagrams. - Use figure identifiers for rendered mermaid / plantuml / aavg diagrams, allowing deletion of stale renders. - Collate all rendered output (including latex cache files) to a .cache/ directory. - Fix permissions for cached/generated files.
1 parent f28796e commit 5a8f24a

10 files changed

Lines changed: 326 additions & 205 deletions

.gitignore

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
11
*.pdf
22
*.docx
33
*.html
4-
/*.tex
5-
*.log
6-
*.aux
7-
*.lof
8-
*.lot
9-
*.toc
10-
*.fdb_latexmk
11-
*.upa
12-
*.upb
4+
.cache/*

Dockerfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,14 @@ RUN apt update && apt install -y fontconfig && \
102102

103103
RUN apt install -y \
104104
bash \
105+
default-jre \
105106
moreutils \
106107
nodejs \
107108
npm \
109+
rsync \
108110
sed \
109-
software-properties-common
111+
software-properties-common \
112+
wget
110113

111114
# Install Chromium via custom repo
112115
# https://askubuntu.com/questions/1204571/how-to-install-chromium-without-snap/1511695#1511695
@@ -119,6 +122,8 @@ ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
119122

120123
RUN npm install --global --unsafe-perm puppeteer@23.2.1 imgur@2.4.2 @mermaid-js/mermaid-cli@11.1.1 typescript@5.5.4 pandiff@0.6.0
121124

125+
RUN wget -O /usr/share/plantuml.jar https://github.com/plantuml/plantuml/releases/download/v1.2025.2/plantuml-asl-1.2025.2.jar
126+
122127
# Important: /usr/local/texlive/bin/ paths come before other paths. We want to use the texlive we
123128
# built above, not any that happen to have come along with our base image.
124129
ENV PATH="/usr/local/texlive/bin/aarch64-linux:/usr/local/texlive/bin/x86_64-linux:${PATH}"
@@ -205,7 +210,6 @@ RUN apt install -y \
205210
libsecret-1-0 \
206211
libxss1 \
207212
openbox \
208-
wget \
209213
xorg \
210214
xvfb
211215

build.sh

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ if [[ -d /extra_resources ]]; then
264264
fi
265265
cd "${BUILD_DIR}"
266266

267+
readonly src_uidgid=$(stat -c "%u:%g" "${SOURCE_DIR}")
268+
269+
# Directory to hold rendered images and other cache files.
270+
mkdir -p ".cache/"
271+
267272
# Let git work
268273
git config --global --add safe.directory "${BUILD_DIR}"
269274

@@ -711,20 +716,17 @@ analyze_latex_logs() {
711716
fi
712717
}
713718

714-
# Copy generated files (if any) back to the source directory so they can be cached and speed up future runs.
719+
# Copy file(s), assuming ownership of SOURCE_DIR.
720+
cp_chown() {
721+
local src=$1
722+
local dst=$2
723+
724+
rsync --archive --mkpath --chown="${src_uidgid}" "${src}" "${dst}"
725+
}
726+
727+
# Sync generated files (if any) back to the source directory so they can be cached and speed up future runs.
715728
cache_generated_files() {
716-
find . -type f \( \
717-
-name "*.aux" -o \
718-
-name "*.lof" -o \
719-
-name "*.lot" -o \
720-
-name "*.toc" -o \
721-
-name "*.upa" -o \
722-
-name "*.upb" -o \
723-
-name "*.convert.pdf" -o \
724-
-name "*.mermaid.pdf" -o \
725-
-name "*.mermaid.svg" -o \
726-
-name "*.aasvg.pdf" \
727-
\) -exec cp --parents {} "${SOURCE_DIR}" \; 2>/dev/null
729+
cp_chown .cache "${SOURCE_DIR}"
728730
}
729731

730732
# Takes Markdown input and writes LaTeX output using pandoc.
@@ -744,11 +746,11 @@ do_latex() {
744746
--standalone
745747
--no-highlight
746748
--template=${TEMPLATE_PDF}
747-
--lua-filter=mermaid-filter.lua
748-
--lua-filter=aasvg-filter.lua
749-
--lua-filter=informative-sections.lua
749+
--lua-filter=convert-diagrams.lua
750+
--lua-filter=convert-aasvg.lua
750751
--lua-filter=convert-images.lua
751752
--lua-filter=center-images.lua
753+
--lua-filter=informative-sections.lua
752754
--lua-filter=parse-html.lua
753755
--lua-filter=apply-classes-to-tables.lua
754756
--lua-filter=landscape-pages.lua
@@ -786,6 +788,10 @@ do_latex() {
786788
fi
787789
local end=$(date +%s)
788790
echo "Elapsed time: $(($end-$start)) seconds"
791+
792+
# Cache generated files now so they don't need to be
793+
# re-rendered if the build later fails.
794+
cache_generated_files
789795
}
790796

791797
# Takes LaTeX input and writes PDF output and logs using the PDF engine of choice.
@@ -807,7 +813,7 @@ do_pdf() {
807813
if [[ "${ignore_errors}" = "true" ]]; then
808814
pdflatex="${pdflatex} -interaction=nonstopmode"
809815
fi
810-
latexmk "${input}" -pdflatex="${pdflatex}" -pdf -diagnostics > "${logfile}"
816+
latexmk "${input}" -pdflatex="${pdflatex}" -pdf -diagnostics -output-directory=.cache/ > "${logfile}"
811817
if [ $? -ne 0 ]; then
812818
FAILED=true
813819
echo "PDF output failed"
@@ -816,12 +822,11 @@ do_pdf() {
816822
# Write any LaTeX errors to stderr.
817823
>&2 grep -A 5 "] ! " "${logfile}"
818824

819-
cache_generated_files
820825
echo "Elapsed time: $(($end-$start)) seconds"
821826
# Write any LaTeX errors to stderr.
822827
>&2 grep -A 5 "! " "${logfile}"
823828
if [[ ! "${FAILED}" = "true" || "${ignore_errors}" = "true" ]]; then
824-
mv "${temp_pdf_file}" "${output}"
829+
cp_chown ".cache/${temp_pdf_file}" "${output}" && rm ".cache/${temp_pdf_file}"
825830
fi
826831
if [[ ! "${FAILED}" = "true" ]]; then
827832
analyze_latex_logs "${logfile}"
@@ -852,8 +857,8 @@ do_docx() {
852857
cmd=(pandoc
853858
--embed-resources
854859
--standalone
855-
--lua-filter=mermaid-filter.lua
856-
--lua-filter=aasvg-filter.lua
860+
--lua-filter=convert-diagrams.lua
861+
--lua-filter=convert-aasvg.lua
857862
--lua-filter=convert-images.lua
858863
--lua-filter=parse-html.lua
859864
--lua-filter=apply-classes-to-tables.lua
@@ -876,8 +881,6 @@ do_docx() {
876881
else
877882
echo "DOCX output generated to file: ${output}"
878883
fi
879-
880-
cache_generated_files
881884
}
882885

883886
# Takes Markdown input and writes HTML output using pandoc.
@@ -897,8 +900,8 @@ do_html() {
897900
--standalone
898901
--template=${TEMPLATE_HTML}
899902
${HTML_STYLESHEET_ARGS}
900-
--lua-filter=mermaid-filter.lua
901-
--lua-filter=aasvg-filter.lua
903+
--lua-filter=convert-diagrams.lua
904+
--lua-filter=convert-aasvg.lua
902905
--lua-filter=parse-html.lua
903906
--lua-filter=apply-classes-to-tables.lua
904907
--lua-filter=landscape-pages.lua
@@ -932,7 +935,6 @@ do_html() {
932935
FAILED=true
933936
echo "HTML output failed"
934937
else
935-
cache_generated_files
936938
echo "HTML output generated to file: ${output}"
937939
fi
938940
}
@@ -945,7 +947,7 @@ if [ -n "${PDF_OUTPUT}" -o -n "${LATEX_OUTPUT}" -o -n "${DIFFPDF_OUTPUT}" -o -n
945947
do_latex "${BUILD_DIR}/${INPUT_FILE}" "${TEMP_TEX_FILE}" "${CROSSREF_TYPE}" "${EXTRA_PANDOC_OPTIONS}"
946948
fi
947949
if [ -n "${LATEX_OUTPUT}" ]; then
948-
cp "${TEMP_TEX_FILE}" "${SOURCE_DIR}/${LATEX_OUTPUT}"
950+
cp_chown "${TEMP_TEX_FILE}" "${SOURCE_DIR}/${LATEX_OUTPUT}"
949951
fi
950952

951953
# Generate the PDF output
@@ -955,8 +957,7 @@ if [ -n "${PDF_OUTPUT}" ]; then
955957

956958
# Copy the logs, if requested.
957959
if [ -n "${PDFLOG_OUTPUT}" ]; then
958-
mkdir -p "$(dirname ${SOURCE_DIR}/${PDFLOG_OUTPUT})"
959-
cp "${LATEX_LOG}" "${SOURCE_DIR}/${PDFLOG_OUTPUT}"
960+
cp_chown "${LATEX_LOG}" "${SOURCE_DIR}/${PDFLOG_OUTPUT}"
960961
fi
961962
fi
962963

@@ -1000,8 +1001,7 @@ if [ -n "${DIFFPDF_OUTPUT}" -o -n "${DIFFTEX_OUTPUT}" ]; then
10001001
else
10011002
do_diff_tex_fixups "${TEMP_DIFF_TEX_FILE}"
10021003
if [ -n "${DIFFTEX_OUTPUT}" ]; then
1003-
mkdir -p "$(dirname ${SOURCE_DIR}/${DIFFTEX_OUTPUT})"
1004-
cp "${TEMP_DIFF_TEX_FILE}" "${SOURCE_DIR}/${DIFFTEX_OUTPUT}"
1004+
cp_chown "${TEMP_DIFF_TEX_FILE}" "${SOURCE_DIR}/${DIFFTEX_OUTPUT}"
10051005
fi
10061006
fi
10071007
fi
@@ -1029,5 +1029,8 @@ if [ "${PRE_DIFFING_FAILED}" == "true" ]; then
10291029
exit 1
10301030
fi
10311031

1032+
# Do a final sync for any later generated files.
1033+
cache_generated_files
1034+
10321035
echo "Overall workflow succeeded"
10331036
exit 0
Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,40 @@
11
-- Turn aasvg-classed code blocks into figures, retaining other classes on the
22
-- code block as classes on the figure.
33

4-
function runCommandWithInput(command, input)
5-
local pipe = io.popen(command, "w")
6-
if not pipe then
7-
return false
8-
end
9-
pipe:write(input)
10-
pipe:flush()
11-
pipe:close()
12-
return true
13-
end
14-
15-
function runCommandSuppressOutput(command)
16-
-- N.B.: we are using io.popen so we can suppress the output of the command.
17-
local pipe = io.popen(command)
18-
if not pipe then
19-
return false
20-
end
21-
pipe:flush()
22-
local output = pipe:read("*all")
23-
pipe:close()
24-
return true
25-
end
26-
27-
function getContentsHash(contents)
28-
return pandoc.sha1(contents):sub(1,10)
29-
end
30-
31-
function fileExists(file)
32-
local f = io.open(file)
33-
if f then
34-
f:close()
35-
return true
36-
end
37-
return false
38-
end
4+
-- Patch the path to include the current script's directory.
5+
package.path = package.path .. ";" .. debug.getinfo(1).source:match("@?(.*/)") .. "?.lua"
6+
utils = require "utils"
397

408
function aasvgFigure(code, caption, attrs)
41-
local filename_base = getContentsHash('code=' .. code .. 'caption=' .. pandoc.utils.stringify(caption) .. 'attrs=' .. pandoc.utils.stringify(attrs)) .. '.aasvg'
9+
local filename_base = '.cache/' .. attrs.identifier .. '.' .. utils.getContentsHash('code=' .. code .. 'caption=' .. pandoc.utils.stringify(caption) .. 'attrs=' .. pandoc.utils.stringify(attrs)) .. '.aasvg'
4210
local filename_svg = filename_base .. '.svg'
4311
local filename_pdf = filename_base .. '.pdf'
4412

45-
if fileExists(filename_pdf) then
46-
print(string.format('%s already exists; not re-rendering it', filename_pdf))
13+
if utils.fileExists(filename_pdf) then
14+
print(string.format(" not converting %s (already up-to-date as %s)", attrs.identifier, filename_pdf))
4715
else
4816
print(string.format('rendering %s using aasvg ...', filename_svg))
49-
if not runCommandWithInput(string.format(
17+
if not utils.runCommandWithInput(string.format(
5018
"aasvg --fill > %s 2>&1", filename_svg), code) then
5119
print(string.format('failed to convert ASCII art SVG (aasvg) diagram to %s using aasvg, falling back to letting LaTeX try to pick it up', filename_svg))
5220
return false
21+
else
22+
print(string.format(" rendered %s to %s", attrs.identifier, filename_svg))
5323
end
5424

5525
print(string.format('converting %s to %s using rsvg-convert ...', filename_svg, filename_pdf))
56-
if not runCommandSuppressOutput(string.format("rsvg-convert --format=pdf --keep-aspect-ratio --output %s %s 2>&1", filename_pdf, filename_svg)) then
26+
if not utils.runCommandSuppressOutput(string.format("rsvg-convert --format=pdf --keep-aspect-ratio --output %s %s 2>&1", filename_pdf, filename_svg)) then
5727
print(string.format('failed to convert %s to %s using rsvg-convert, falling back to letting LaTeX try to pick it up', filename_svg, filename_pdf))
5828
return false
29+
else
30+
print(string.format(' converted %s to %s', filename_svg, filename_pdf))
31+
end
32+
33+
-- Delete stale copies of the files. This makes it easier to cache only the latest converted pdfs.
34+
-- Don't do this if the "keepstaleimages" variable is set.
35+
if not PANDOC_WRITER_OPTIONS.variables["keepstaleimages"] then
36+
utils.deleteFilesExcept('.cache/' .. attrs.identifier .. '*.aasvg.svg*', filename_svg)
37+
utils.deleteFilesExcept('.cache/' .. attrs.identifier .. '*.aasvg.pdf*', filename_pdf)
5938
end
6039
end
6140

0 commit comments

Comments
 (0)