Reanimator revived

Over a year ago, I hit a dead end on Windows 10 with David Himmelstrup’s reanimate package. I returned to the package with a simple animation in mind. In the interim, things had moved on. I had no difficulty creating the animation and rendering it to a GIF image:


On Windows 10, the Haskell Tool Stack provides MSYS2 on the path in the stack exec environment. The MSYS2 package manager (pacman) can be used to install FFmpeg (executable ffmpeg), rsvg-convert (provided with package librsvg), and ImageMagick (executable magick):

The versions of FFmpeg available on Windows 10 are, however, not compiled with option --enable-librsvg. This means that the Has ffmpeg(rsvg) dependency is no.

POV-Ray for Windows, Blender and Inkscape can each be downloaded and its executable’s location added to the PATH.

LaTeX can be downloaded via the MikTex project, which also includes the dvisvgm tool. The ctex package requires the font SimHei, which is provided by installing the Windows Language Pack for Chinese (Simplified, China).

With the dependencies in place, almost all of the reanimate checks passed:

POV-Ray for Windows

POV-Ray is described in the reanimate documentation as one of the pillars on which the package is built. However, I discovered that using POV-Ray for Windows with the package was not straightforward.

On Unix-like operating systems, POV-Ray is a command-line application and the executable is named povray. On Windows, POV-Ray uses a graphical user interface (GUI) and the 64-bit version of the executable is named pvengine64.exe.

pvengine64 can be used from the command prompt. However, it needs a special command-line option /EXIT. As the POV-Ray documentation explains: the /EXIT command tells POV-Ray to perform the render given by the other command-line options, combined with previously-set options such as internal command-line settings, INI file, source file, and so forth, and then to exit. By default, if this switch is not present, POV-Ray for Windows will remain running after the render is complete.

The reanimate package was making use of povray via System.Process.readProcessWithExitCode, but I could not get pvengine64 to do the same. It seemed to be interpreting the first argument as the name of a POV-Ray INI file.

On Windows 10, readProcessWithExitCode leads to System.Process.Windows.createProcess_Internal_wrapper and its use of commandToProcess and translateInternal. translateInternal wraps arguments in quotation marks:

The foldr works from right to left along the string, escaping backslash characters (if the ‘quotation’ mode is on) and double quotation mark characters. ‘Quotation` mode is on to begin with, is turned on by a quotation character, and is turned off by a character that is not a backslash or quotation character. So, +A translates as "+A", and C:\Program Files\POV-Ray\v3.7\bin\pvengine64.exe translates as "C:\Program Files\POV-Ray\v3.7\bin\pvengine.exe".

This escaping appears to be consistent with the parsing carried out by Microsoft’s CommandLineToArgW function. However, POV-Ray for Windows does not appear to use that function to parse a command line but a C function parse_commandline in source code file pvengine.cpp.

The C algorithm is convoluted, but if s points to a null-terminated "+A", then prevWord will point to the first quotation character and argv[1] will point to null-terminated "+A". That is, the quotation marks around +A are preserved by the parser. This can also be seen in the output of the POV-Ray for Windows program in the Messages window.

This appears to be problematic, because POV-Ray expects command-line switches to begin with a + or a -, and everything else is assumed to be a path to a POV-Ray INI file. The main function in source file povmain.cpp includes the following code:

ParseString is applied to each of the argv in turn, with the single switch flag set to true.

ProcessRenderOptions is a subclass of ProcessOptions, which class provides the function ParseString:

The switch based on the first character pointed to by commandline shows that only +, -, and / (on Windows) get to Parse_CL_Switch, while " and ' (via err = kFalseErr) get to Parse_CL_String.

The work around (or, at least, the immediate one) for reanimate was to write the pvengine64 command to a temporary batch file and then use readProcessWithExitCode to run that batch file as a command. I also considered using readCreateProcessWithExitCode $ shell command (which uses CMD.EXE \C <command>) but, surprisingly, that seemed to about 5% slower.