I use Hugo to generate this blog and I love it, easy to use, easy to extend with shortcuts, I just love it. Most of the time I don’t use images, because that would require me to open an image editor, save the file, export it as a PNG, upload to my Minio server and use the image in the post. I wouldn’t mind the extra step or the fact I have to work with it, but opening an external program is not my life. Another downside, I want to change an image, or reuse given parts of an existing image can be hard like two years later. I know I have the Krita file somewhere, but I have no idea where, or I spend like 3 hours to find it.
One day I wanted to create a graph and I was about to start Krita, but suddenly realized I have a tool for that and it’s called LaTeX.
The story could end here, but not. My brain said “you can use this to create documents or images” and sure, I can. I started to experiment generating images, and it worked and I can manage the images in Git and that’s awesome. As an extra, I can generate images with transparent background, so when I include them in a post, the image “responds” to the active theme, the only thing I have to take care is to use good colors, so they are be visible both on light and dark background. I can still generate the same image from the same source file with filled background for open graph cover image.
Generate images
So I have a plan, build cover images with TeX. Writing the healer section in every single file would be silly, so let’s create a template first:
\documentclass[convert={density=300}]{standalone}
% Import commonly used packages.
\usepackage{tikz}
\usepackage{transparent}
\usepackage{fontspec}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage{anyfontsize}
\usetikzlibrary{calc, backgrounds}
% Set fonts, so it looks good.
\setmainfont{Umpush-Light.otf}
\setmonofont{Sauce Code Pro Nerd Font Complete.ttf}[
Path = /home/efertone/.fonts/
]
% Ignore this now, we will come back later.
\graphicspath{{@@IMGPATH@@}}
% Generate colors
\definecolor{foreground}{RGB}{169, 169, 179}
\definecolor{background}{RGB}{41, 42, 45}
% Include a document here.
\input{@@TARGET@@}
Now we can create a script that finds all the .tex
files and generates an
image with this template. To organize them, I have a tex
directory in the root
of the repository that looks like this:
tex
├── 2020
│ └── 12
│ └── advent-of-code-2020-day-14
│ └── cover.tex
├── 2022
│ ├── 2022-02-02-go-1-18-generics
│ │ ├── cover.tex
│ │ └── foldl-vs-foldr.tex
│ └── 2022-02-04-how-i-generate-images-with-tex
│ ├── cover.tex
│ ├── hugo.png
│ ├── hugo.svg
│ └── readme.md
├── advent-of-vim
│ └── 2021
│ └── day12-01.tex
└── template.tex
8 directories, 9 files
How can we use the template with one of the .tex
files? We can use sed
magic, that’s why we have a @@TARGET@@
in the template. First create a
temporary directory because it generates a lot of files while compiling and we
don’t need them.
# Create a temp directory.
tmpDir=$(mktemp -d)
# Delete the directory when we are done.
trap "rm -rf ${tmpDir}" EXIT
# Find all the .tex files, but ignore the template.
for file in $(find ./tex -name "*.tex" -not -name 'template.tex'); do
# Extract the path, but without the tex/ top level directory.
filePath=$(dirname $file | sed -e 's#^tex/##')
# Generate a temp path from the original path.
tmpPath="${tmpDir}/${filePath}"
# Get the filename without the extension.
fileName="$(basename ${file} '.tex')"
# Create the full path under our temp directory.
mkdir -p "${tmpPath}"
# Generate the output pdf.
# Set output directory to our temp directory.
# Set job name to the filename (without extension).
# We need this because xelatex has no idea what is the filename
# from stdin.
cat tex/template.tex \
| sed -e "s#@@TARGET@@#${file}#" \
| xelatex \
-output-directory="${tmpPath}" \
-jobname="${fileName}"
done
Now we have a nice PDF in out temp directory. Actually we don’t have that file
because we delete the whole directory at the end, but that’s fine. Why
xelatex
and not latex
? Because xelatex
supports fancy external fonts
without extra hacks.
Now before we delete the directory, we want to convert the PDF into PNG. We have a nice tool to do that, it’s called ImageMagick. So let’s convert the PDF to PNG.
convert \
-density 300 \
"${tmpPath}/${fileName}.pdf" \
-quality 90 \
"${tmpPath}/${fileName}.png"
At the same time, we can generate an image with a solid background too.
convert \
-density 300 \
"${tmpPath}/${fileName}.pdf" \
-quality 90 \
-background "#292a2d" \
-flatten \
"${tmpPath}/${fileName}-fill.png"
The only thing left, to move images back before we delete the directory. We
have a tex
directory for .tex
files, we can use tex-build
to keep all the
images. They can be anywhere, but because I use my Minio to host images, I do
not want to track them in git (it’s in my .gitignore
).
mkdir -p "tex-build/${filePath}/"
cp "${tmpPath}/${fileName}.png" "tex-build/${filePath}/"
cp "${tmpPath}/${fileName}-fill.png" "tex-build/${filePath}/"
One big issue, it will generate all the images every time, that can be good
sometimes, but would be nice if we can define targets. Let’s update the find
arguments a bit:
scope="${1:-tex}"
if [[ "${scope}" != tex* ]]; then
scope="tex/${scope}"
fi
for file in $(find ${scope} -name "*.tex" -not -name 'template.tex'); do
If the scope in the argument has not tex
prefix, we add it because we know all
out .tex
files should live under the tex
directory.
Let’s the whole script with extra fancy messages:
#!/usr/bin/env bash
scope="${1:-tex}"
tmpDir=$(mktemp -d)
if [[ "${scope}" != tex* ]]; then
scope="tex/${scope}"
fi
trap "rm -rf ${tmpDir}" EXIT
for file in $(find ${scope} -name "*.tex" -not -name 'template.tex'); do
filePath=$(dirname $file | sed -e 's#^tex/##')
tmpPath="${tmpDir}/${filePath}"
fileName="$(basename ${file} '.tex')"
mkdir -p "${tmpPath}"
echo -en " \U1F550 ${filePath}/${fileName}.tex ..."
cat tex/template.tex \
| sed -e "s#@@TARGET@@#${file}#" \
-e "s#@@IMGPATH@@#$(pwd)/tex/${filePath}/#" \
| xelatex \
-output-directory="${tmpPath}" \
-jobname="${fileName}" \
> /dev/null
convert \
-density 300 \
"${tmpPath}/${fileName}.pdf" \
-quality 90 \
"${tmpPath}/${fileName}.png"
convert \
-density 300 \
"${tmpPath}/${fileName}.pdf" \
-quality 90 \
-background "#292a2d" \
-flatten \
"${tmpPath}/${fileName}-fill.png"
mkdir -p "tex-build/${filePath}/"
cp "${tmpPath}/${fileName}.png" "tex-build/${filePath}/"
cp "${tmpPath}/${fileName}-fill.png" "tex-build/${filePath}/"
echo -e "\r \U2705 ${filePath}/${fileName}.tex -> ${filePath}/${fileName}[-fill].png"
done
Upload them
One of the reasons why we put the generates images in the tex-build
with their
original path, we can upload them to S3/Wasabi/Minio/whatever with the whole
directory structure.
#!/usr/bin/env bash
pattern=${1}
for file in $(find tex-build -name "*.png"); do
filePath=$(dirname $file | sed -e 's#^tex-build/##')
fileName="$(basename ${file})"
case "${file}" in
*"${pattern}"*)
;;
*)
continue
;;
esac
mc cp "${file}" "spaces/static.efertone.me/blog/${filePath}/${fileName}"
done
I know, I can use mc mirror
, but I have other plans for the future and it’s
easier to use mc cp
now.
Some examples
Cover image | Go 1.18: Fold with generics
A nice cover image for the Go 1.18: Fold with generics:
\definecolor{blue}{RGB}{0, 125, 156}
\definecolor{mark}{RGB}{127, 156, 0}
\begin{document}
\begin{tikzpicture}[
inner xsep=20pt, inner ysep=45pt,
scale=1
]
\node[text=foreground, scale=2]
{
{\tt func [ \textcolor{mark}{T} }
\textcolor{blue}{\Large Generics}
{\tt ] ( arg \textcolor{mark}{T} )}
};
\end{tikzpicture}
\end{document}
Regexp explanation | Advent of Vim: Subtitution magic
And here how I generated the regexp summary image in the Advent of Vim: Subtitution magic post.
\begin{document}
\begin{tikzpicture}[draw=foreground, text=foreground]
\node at (0, 1) (1) {[a-z]+};
\node[anchor=west, xshift=10pt] at (1.east) (2) {-};
\node[anchor=west, xshift=10pt] at (2.east) (3) {[0-9]\{2\}};
\node[anchor=west, xshift=10pt] at (3.east) (4) {-};
\node[anchor=west, xshift=10pt] at (4.east) (5) {[a-z]+};
\node at (2, 3) (1desc) {Any lower case letters at least once};
\node at (2, 0) (3desc) {A 2 digits number};
\node at (2, 2) (hyphen) {Literal hyphen};
\draw [->] (1desc) -- (0,2) -- (1);
\draw [->] (1desc) -- (4.7,2) -- (5);
\draw [->] (hyphen) -- (2);
\draw [->] (hyphen) -- (4);
\draw [->] (3desc) -- (3);
\end{tikzpicture}
\end{document}
The cover image of this post
This post was the first when I wanted to embed an image and because I don’t wan
to copy over static images into the temp directory, I simply set the
\graphicspath
to the original path of the .tex
file, that way I can include
them with \includegraphics
. So that’s why I have the
\graphicspath{{@@IMGPATH@@}}
line in the template and the extra sed
substitution in the script.
\newcommand\epic{\fontsize{64pt}{64pt}\selectfont}
\definecolor{heartcolor}{RGB}{200, 147, 187}
\begin{document}
\begin{tikzpicture}[
inner xsep=100pt,
inner ysep=25pt
]
\node[text=foreground] (latex) {\epic \LaTeX};
\node[anchor=north, xshift=150pt] (hugo) at (latex.south)
{\includegraphics[scale=0.3]{hugo.png}};
\begin{scope}[yshift=-80pt, xshift=150pt]
\draw[draw=none, fill=heartcolor] (0,0) .. controls (0,0.75)
and (-1.5,1.00) .. (-1.5,2) arc (180:0:0.75);
\draw[draw=none, fill=heartcolor] (0,0) .. controls (0,0.75)
and ( 1.5,1.00) .. ( 1.5,2) arc (0:180:0.75);
\end{scope}
\end{tikzpicture}
\end{document}