LatexEngine.java
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2025, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.tikz;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sourceforge.plantuml.skin.Pragma;
import net.sourceforge.plantuml.skin.PragmaKey;
public enum LatexEngine {
LUALATEX, XELATEX, PDFLATEX, UNKNOWN_OR_NOT_INSTALLED, NONE;
// Force NONE in tests (e.g. Vega) so rendering does not depend on whether a
// LaTeX distribution happens to be installed on the build machine.
public static boolean FORCE_NONE = false;
private static final Map<String, Boolean> isInstalledCache = new ConcurrentHashMap<String, Boolean>();
public static LatexEngine getSuggestedEngine(Pragma pragma) {
if (FORCE_NONE)
return NONE;
final String value = pragma.getValue(PragmaKey.TEX_SYSTEM);
if (value != null) {
if ("lualatex".equalsIgnoreCase(value) && isInstalled("lualatex"))
return LUALATEX;
if ("xelatex".equalsIgnoreCase(value) && isInstalled("xelatex"))
return XELATEX;
if ("pdflatex".equalsIgnoreCase(value) && isInstalled("pdflatex"))
return PDFLATEX;
return UNKNOWN_OR_NOT_INSTALLED;
}
if (isInstalled("lualatex"))
return LUALATEX;
if (isInstalled("xelatex"))
return XELATEX;
// Last resort: pdflatex has no native unicode/fontspec support like lualatex
// and xelatex, but it is often the only engine present on minimal TeX
// installations, so it is worth trying before giving up on LaTeX/TikZ
// output entirely.
if (isInstalled("pdflatex"))
return PDFLATEX;
return NONE;
}
public String getCommand() {
switch (this) {
case LUALATEX:
return "lualatex";
case XELATEX:
return "xelatex";
case PDFLATEX:
return "pdflatex";
}
throw new UnsupportedOperationException();
}
private static boolean isInstalled(String command) {
return isInstalledCache.computeIfAbsent(command, LatexEngine::probeInstalled);
}
private static boolean probeInstalled(String command) {
try {
final Process process = new ProcessBuilder(command, "--version").redirectErrorStream(true).start();
// Drain output so the process can terminate, then wait for it.
drain(process.getInputStream());
final int exit = process.waitFor();
return exit == 0;
} catch (IOException e) {
// "Cannot run program": binary not on PATH
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
private static void drain(InputStream is) throws IOException {
final byte[] buffer = new byte[4096];
while (is.read(buffer) != -1) {
// discard
}
}
}