External LaTeX packages | Aspose.TeX for Java

External LaTeX packages

There is a number of common LaTeX packages included in the Aspose.TeX library for Java. So you don’t have to take care about how to provide these packages to the library’s TeX engine. But sometimes (maybe often) your LaTeX file may require a package beyond the “natively” supported “bundle” of packages. In this case, you can try to provide the required input, i.e. required package’s source files, using the setRequiredInputDirectory() method of the TeXOptions class instance. We will see how this works with two examples.

Set inputFile to the full path of the corresponding LaTeX document and outputDirectory to an existing output folder. Both conversion examples produce PNG at 150 DPI. Import the Aspose.TeX classes used in the selected snippet, com.aspose.tex.rendering.ImageDevice, com.aspose.tex.rendering.PngSaveOptions and, for ZIP input, java.io.InputStream and java.nio.file.Files. Handle or declare exceptions in the calling method. Apply a license as described in the licensing guide.

Unpacked required input (fancybox package)

Let’s say we have the following simple LaTeX file, which is required-input-fs.tex from our example solution:

 1\documentclass{article}
 2\usepackage[a6paper,landscape]{geometry}
 3\usepackage{fancybox}
 4\begin{document}
 5Test: \fbox{
 6  \begin{Bitemize}[b]
 7  \item First item
 8  \item A second one\\ on two lines
 9  \item(2pt) A third with extra space
10  \end{Bitemize}
11}
12\par\bigskip
13Test: \fbox{
14  \begin{Beqnarray}
15  y & = & x^2 \\
16  a^2 + 2ab + b^2 & = & (a + b)^2 \\
17  \int_0^\infty e^{-ax} dx & = & \frac{1}{a}
18  \end{Beqnarray}
19}
20\end{document}

The third line requires the external fancybox package. Set packagesDirectory to a local folder containing fancybox.sty and its dependencies. The complete example below assigns this folder to RequiredInputDirectory and produces a PNG image. The device and working directories remain open until the job finishes and are then disposed or closed.

Output Document

Here is the complete source code for the example:

 1// Render the fancybox document using external package sources from a local folder.
 2// inputFile is the full TeX input path; packagesDirectory contains fancybox.sty.
 3// outputDirectory is an existing output folder.
 4TeXOptions options = TeXOptions.consoleAppOptions(TeXConfig.objectLaTeX());
 5OutputFileSystemDirectory output = new OutputFileSystemDirectory(outputDirectory);
 6InputFileSystemDirectory packages = new InputFileSystemDirectory(packagesDirectory);
 7options.setOutputWorkingDirectory(output);
 8options.setRequiredInputDirectory(packages);
 9PngSaveOptions saving = new PngSaveOptions();
10saving.setResolution(150);
11options.setSaveOptions(saving);
12options.setInteraction(Interaction.NonstopMode);
13options.setTerminalOut(new OutputMemoryTerminal());
14ImageDevice device = new ImageDevice();
15TeXJobResult result;
16int pages;
17try {
18    result = new TeXJob(inputFile, device, options).run();
19    pages = device.getPageCount();
20} finally {
21    device.dispose();
22    output.close();
23    packages.close();
24}

Archived required input (pgfplots package)

Let’s now assume that we have the following also quite simple LaTeX file, which is required-input-zip.tex from our example solution:

 1\documentclass{article}
 2\usepackage[margin=0.25in]{geometry}
 3\usepackage{pgfplots}
 4\pgfplotsset{width=10cm,compat=1.18}
 5\begin{document}
 6
 7First example is 2D and 3D math expressions plotted side-by-side.
 8
 9%Here begins the 2D plot
10\begin{tikzpicture}
11\begin{axis}
12\addplot[color=red]{exp(x)};
13\end{axis}
14\end{tikzpicture}
15%Here ends the 2D plot
16\hskip 5pt
17%Here begins the 3D plot
18\begin{tikzpicture}
19\begin{axis}
20\addplot3[
21    surf,
22]
23{exp(-x^2-y^2)*x};
24\end{axis}
25\end{tikzpicture}
26%Here ends the 3D plot
27
28\end{document}

On the 3rd line, we see that the file requires the pgfplots package, which is also not “natively” supported. Again, we assume that we have the pgfplots package source files. It’s quite a large set of files that are divided between two locations if you find them in the installation directory of any LaTeX typesetting application. You can find pgfplots folder at both \tex\generic and \tex\latex folders. And the contents of both these folders must be provided as required input to the Aspose.TeX library. We want these source file to be packaged in a ZIP archive, so here is the archive’s layout:

archive’s layout

Set packagesZip to a java.nio.file.Path pointing to the archive containing both the generic and LaTeX package sources. The complete Java example below opens it with Files.newInputStream(...) and assigns an InputZipDirectory to RequiredInputDirectory. The empty base-path argument selects the whole archive. Keep the ZIP stream and directory open until the TeX job finishes, then close them. The result is a PNG image with the two plots.

Output document

Here is the complete source code for the example:

 1// Render the 2D/3D plots using pgfplots package sources from a ZIP archive.
 2// inputFile is the full TeX input path; packagesZip is a java.nio.file.Path.
 3// outputDirectory is an existing output folder; the ZIP includes generic and latex sources.
 4TeXJobResult result;
 5int pages;
 6try (InputStream zipStream = Files.newInputStream(packagesZip)) {
 7    TeXOptions options = TeXOptions.consoleAppOptions(TeXConfig.objectLaTeX());
 8    OutputFileSystemDirectory output = new OutputFileSystemDirectory(outputDirectory);
 9    InputZipDirectory packages = new InputZipDirectory(zipStream, "");
10    options.setOutputWorkingDirectory(output);
11    options.setRequiredInputDirectory(packages);
12    PngSaveOptions saving = new PngSaveOptions();
13    saving.setResolution(150);
14    options.setSaveOptions(saving);
15    options.setInteraction(Interaction.NonstopMode);
16    options.setTerminalOut(new OutputMemoryTerminal());
17    ImageDevice device = new ImageDevice();
18    try {
19        result = new TeXJob(inputFile, device, options).run();
20        pages = device.getPageCount();
21    } finally {
22        device.dispose();
23        output.close();
24        packages.close();
25    }
26}

NOTE: The result was verified using the pgfplots package version 1.18.1. While the version of the pfg package included in the Aspose.TeX library is 3.1.9a.

Using external font packages

Typically, LaTeX distributions come with a selection of default fonts that can be used for typesetting. In addition to these fonts, Aspose.TeX offers several non-standard font packages, such as amsfonts, eurosym, and wasysym. As mentioned earlier, Aspose.TeX also supports the use of external packages, although our focus has mainly been on packages that do not include fonts.

The Object TeX engine extension in Aspose.TeX requires font maps, which are text files with the .map extension that specify the physical fonts corresponding to each TeX’s internal font name. These font maps need to be loaded into TeX’s memory during the initialization phase and must be included in the package files. Since the engine is not aware of the names of all font maps, it searches for any files with the .map extension. Therefore, the implementation of the IInputWorkingDirectory interface used as the value of the RequiredInputDirectory option must provide a way to access collections of file names based on their extension. Specifically, this implementation must also implement the IFileCollector interface. The standard implementations, InputFileSystemDirectory and InputZipDirectory, already fulfill this requirement.

The following custom RequiredInputDirectory implements both interfaces and reads explicitly registered local files. Save it as RequiredInputDirectory.java with its included imports. Register each file with storeFileName(...), then assign the instance using options.setRequiredInputDirectory(...) and close it after the job. It is a flat registry: filenames must be unique when requested without a full path. Extension matching is case-insensitive; the optional collection path selects a file-system directory. Register all required package, font-map, metric and physical font files. File collection makes maps discoverable; it does not supply missing font data.

 1import com.aspose.tex.IInputWorkingDirectory;
 2import com.aspose.tex.IFileCollector;
 3import com.aspose.tex.TeXInputStream;
 4import java.io.IOException;
 5import java.io.UncheckedIOException;
 6import java.nio.file.Files;
 7import java.nio.file.Path;
 8import java.nio.file.Paths;
 9import java.util.ArrayList;
10import java.util.LinkedHashSet;
11import java.util.List;
12import java.util.Locale;
13import java.util.Set;
14
15// Register local package files explicitly, including font maps and their dependencies.
16// Save this class as RequiredInputDirectory.java and close it after the TeX job.
17public class RequiredInputDirectory implements IInputWorkingDirectory, IFileCollector {
18    private final Set<Path> files = new LinkedHashSet<Path>();
19    private final List<TeXInputStream> streams = new ArrayList<TeXInputStream>();
20
21    public void storeFileName(String fileName) {
22        Path file = Paths.get(fileName).toAbsolutePath().normalize();
23        if (!Files.isRegularFile(file)) {
24            throw new IllegalArgumentException("Package file does not exist: " + file);
25        }
26        files.add(file);
27    }
28
29    @Override
30    public TeXInputStream getFile(String fileName, boolean searchSubdirectories) {
31        // This registry is a flat virtual directory; only registered files are accessible.
32        for (Path file : files) {
33            if (file.toString().equals(fileName) || file.getFileName().toString().equals(fileName)) {
34                try {
35                    TeXInputStream stream = new TeXInputStream(Files.newInputStream(file), file.toString());
36                    streams.add(stream);
37                    return stream;
38                } catch (IOException exception) {
39                    throw new UncheckedIOException(exception);
40                }
41            }
42        }
43        return new TeXInputStream(null, fileName);
44    }
45
46    @Override
47    public String[] getFileNamesByExtension(String extension) {
48        return getFileNamesByExtension(extension, null);
49    }
50
51    @Override
52    public String[] getFileNamesByExtension(String extension, String path) {
53        String requested = extension.toLowerCase(Locale.ROOT);
54        if (!requested.isEmpty() && !requested.startsWith(".")) {
55            requested = "." + requested;
56        }
57        Path directory = path == null ? null : Paths.get(path).toAbsolutePath().normalize();
58        List<String> matches = new ArrayList<String>();
59        for (Path file : files) {
60            String name = file.getFileName().toString();
61            int dot = name.lastIndexOf('.');
62            String actual = dot < 0 ? "" : name.substring(dot).toLowerCase(Locale.ROOT);
63            if (actual.equals(requested) && (directory == null || file.getParent().equals(directory))) {
64                matches.add(file.toString());
65            }
66        }
67        return matches.toArray(new String[0]);
68    }
69
70    @Override
71    public void close() throws IOException {
72        IOException failure = null;
73        for (TeXInputStream stream : streams) {
74            try {
75                stream.close();
76            } catch (IOException exception) {
77                if (failure == null) { failure = exception; }
78                else { failure.addSuppressed(exception); }
79            }
80        }
81        streams.clear();
82        files.clear();
83        if (failure != null) { throw failure; }
84    }
85}

Restrictions

A package required by your LaTeX file may be developed under the LaTeX3e kernel. Such a package will most likely not work with the Aspose.TeX library since the latter is based on the LaTeX2e kernel.

Also, a package required by your LaTeX file may directly call device-dependent primitive commands that are not supported by the Aspose.TeX library’s Object TeX engine. Such a package, unfortunately, will not work for sure.