hf-tikz 和 sphinx 不能一起玩

hf-tikz and sphinx are not playing well together

我正在尝试为 sphinx 中的矩阵添加一些颜色。我之前使用 hf-tikz 。但是,当我将它添加到 Sphinx 时,它呈现不正确。

我想要得到的结果是

我得到的结果是

这是我的代码。

main.rst:

.. math::

    \left(\begin{array}{cc}
        \tikzmarkin[style red]{a}a\tikzmarkend{a}
      & \tikzmarkin[style green]{b}b\tikzmarkend{b} \
        \tikzmarkin[style blue]{c}c\tikzmarkend{c}
      & \tikzmarkin[style orange]{d}d\tikzmarkend{d} \
    \end{array}\right)
    \star
    \left(\begin{array}{cc}
        \tikzmarkin[style red]{w}w\tikzmarkend{w}
      & \tikzmarkin[style green]{x}x\tikzmarkend{x} \
        \tikzmarkin[style blue]{y}y\tikzmarkend{y}
      & \tikzmarkin[style orange]{z}z\tikzmarkend{z} \
    \end{array}\right)
    =
    \left(\begin{array}{cc}
        \tikzmarkin[hor=style red]{aw}{a\star w}\tikzmarkend{aw}
      & \tikzmarkin[hor=style green]{bx}b\star x\tikzmarkend{bx} \
        \tikzmarkin[hor=style blue]{cy}c\star y\tikzmarkend{cy}
      & \tikzmarkin[hor=style orange]{dz}d\star z\tikzmarkend{dz} \
    \end{array}\right)

conf.py

extensions = [
  'sphinx.ext.imgmath',
]

# Math configurations (https://tex.stackexchange.com/a/69770/51173)
imgmath_image_format = 'svg'
imgmath_use_preview = True
imgmath_latex_preamble = r'''
  \usepackage{xcolor}
  \usepackage[customcolors]{hf-tikz}
  \colorlet{myred}{red!50!purple!30}
  \colorlet{mygreen}{green!50!lime!60}
  \colorlet{myblue}{blue!50!white!50}
  \colorlet{myorange}{orange!80!red!60}
  \colorlet{mycyan}{cyan!90!blue!60}
  \colorlet{mymagenta}{magenta!90!red!60}
  \tikzset{
    style red/.style={
      set fill color=myred,
      set border color=white,
    },
    style green/.style={
      set fill color=mygreen,
      set border color=white,
    },
    style blue/.style={
      set fill color=myblue,
      set border color=white,
    },
    style orange/.style={
      set fill color=myorange,
      set border color=white,
    },
    style cyan/.style={
      set fill color=mycyan,
      set border color=white,
    },
    style magenta/.style={
      set fill color=mymagenta,
      set border color=white,
    },
    %
    hor/.style={
      above left offset={-0.15,0.31},
      below right offset={0.15,-0.125},
      #1
    },
    ver/.style={
      above left offset={-0.1,0.3},
      below right offset={0.15,-0.15},
      #1
    }
  }
'''

生成文件

# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS    ?=
SPHINXBUILD   ?= sphinx-build
SOURCEDIR     = source
BUILDDIR      = build

# Put it first so that "make" without argument is like "make help".
help:
    @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
    @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

make.bat

@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
    set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
    echo.
    echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
    echo.installed, then set the SPHINXBUILD environment variable to point
    echo.to the full path of the 'sphinx-build' executable. Alternatively you
    echo.may add the Sphinx directory to PATH.
    echo.
    echo.If you don't have Sphinx installed, grab it from
    echo.http://sphinx-doc.org/
    exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd

编辑:添加了用于构建第一个

的 makefile

我想我找到了发生这种情况的原因:问题出在 sphinx.ext.imgmath 扩展 + hf-tikz 不能很好地处理 DVI 文件。

在转换数学方程式时,sphinx 会创建一个非常基本的乳胶文档,并使用 latexpdf 将其编译成 DVI 文件。之后文件被转换为 SVG,生成的 svg 文件被复制到 sphinx 的 _build 目录。问题是 dvisvgm(imgmath 使用)不能转换 tikz 的东西。替代方法是使用扩展 DVI,但效果也不佳。

解决方案是将所有内容编译成 PDF,然后将该 pdf 转换成 SVG。这有点问题,我找到的唯一方法是使用 pdf2svg + pdfcrop。我最终将 imgmath.py 修改为自定义扩展。以下是我在 imgmath.py 中所做的更改。这些更改需要使用外部应用程序,因此我认为创建拉取请求没有任何好处(至少没有更具可扩展性的解决方案)。

imgmath.py 中的变化:

  1. 创建新方法:
def convert_pdf_to_svg(pdfpath, builder):
    # type: (str, Builder) -> Tuple[str, int]
    """Convert DVI file to SVG image."""
    tempdir = ensure_tempdir(builder)
    filename = path.join(tempdir, 'math.svg')

    name = 'pdfcrop'
    command = [name, pdfpath, pdfpath]
    run_external_command(command, name)

    name = 'pdf2svg'
    command = [name, pdfpath, filename]
    run_external_command(command, name)
    return filename, None
  1. compile_math 函数中,在 try 块内,将 return 语句替换为以下
if builder.config.imgmath_pdf2svg:
  return path.join(tempdir, 'math.pdf')
else:
  return path.join(tempdir, 'math.dvi')
  1. render_math 方法中,在块标题 # .dvi -> .png/svg 中,将 try 块替换为以下
try:
    if image_format == 'png':
        imgpath, depth = convert_dvi_to_png(dvipath, self.builder)
    elif image_format == 'svg':
        if self.builder.config.imgmath_pdf2svg:
            imgpath, depth = convert_pdf_to_svg(dvipath, self.builder)
        else:
            imgpath, depth = convert_dvi_to_svg(dvipath, self.builder)
except InvokeError:
    self.builder._imgmath_warned_image_translator = True  # type: ignore
    return None, None
  1. 最后,在imgmath.py的最后添加一个新的配置项:
    app.add_config_value('imgmath_pdf2svg', False, 'html')

之后可以在conf.py中写入启用tikz图片

imgmath_image_format = 'svg'
imgmath_latex = 'latexmk'
imgmath_latex_args = ['-pdf']
imgmath_pdf2svg = True  # Available only in the custom `imgmath.py`

imgmath 扩展的补丁。 包括一些其他内容:)

补丁 a->b.

--- a/imgmath.py
+++ b/imgmath.py
@@ -15,7 +15,7 @@
 import sys
 import tempfile
 from hashlib import sha1
-from os import path
+from os import path, symlink
 from subprocess import CalledProcessError, PIPE
 from typing import Any, Dict, List, Tuple

@@ -157,6 +157,11 @@
     with open(filename, 'w', encoding='utf-8') as f:
         f.write(latex)

+    for add_file in builder.config.imgmath_latex_additional_files:
+        filename = path.join(tempdir, path.basename(add_file))
+        if not path.exists(filename):
+            symlink(path.join(builder.confdir, add_file), filename)
+
     # build latex command; old versions of latex don't have the
     # --output-directory option, so we have to manually chdir to the
     # temp dir to run it.
@@ -165,9 +170,15 @@
     command.extend(builder.config.imgmath_latex_args)
     command.append('math.tex')

+    output_extension = 'dvi'
+    if builder.config.imgmath_latex == 'xelatex':
+        output_extension = 'xdv'
+    if builder.config.imgmath_pdf2svg:
+        output_extension = 'pdf'
+
     try:
         subprocess.run(command, stdout=PIPE, stderr=PIPE, cwd=tempdir, check=True)
-        return path.join(tempdir, 'math.dvi')
+        return path.join(tempdir, 'math.' + output_extension)
     except OSError:
         logger.warning(__('LaTeX command %r cannot be run (needed for math '
                           'display), check the imgmath_latex setting'),
@@ -177,7 +188,7 @@
         raise MathExtError('latex exited with error', exc.stderr, exc.stdout)


-def convert_dvi_to_image(command: List[str], name: str) -> Tuple[bytes, bytes]:
+def run_external_command(command: List[str], name: str) -> Tuple[bytes, bytes]:
     """Convert DVI file to specific image format."""
     try:
         ret = subprocess.run(command, stdout=PIPE, stderr=PIPE, check=True)
@@ -203,7 +214,7 @@
         command.append('--depth')
     command.append(dvipath)

-    stdout, stderr = convert_dvi_to_image(command, name)
+    stdout, stderr = run_external_command(command, name)

     depth = None
     if builder.config.imgmath_use_preview:
@@ -227,7 +238,7 @@
     command.extend(builder.config.imgmath_dvisvgm_args)
     command.append(dvipath)

-    stdout, stderr = convert_dvi_to_image(command, name)
+    stdout, stderr = run_external_command(command, name)

     depth = None
     if builder.config.imgmath_use_preview:
@@ -239,6 +250,21 @@
                 break

     return filename, depth
+
+def convert_pdf_to_svg(pdfpath, builder):
+    # type: (str, Builder) -> Tuple[str, int]
+    """Convert DVI file to SVG image."""
+    tempdir = ensure_tempdir(builder)
+    filename = path.join(tempdir, 'math.svg')
+
+    name = 'pdfcrop'
+    command = [name, pdfpath, pdfpath]
+    run_external_command(command, name)
+
+    name = 'pdf2svg'
+    command = [name, pdfpath, filename]
+    run_external_command(command, name)
+    return filename, None


 def render_math(self: HTMLTranslator, math: str) -> Tuple[str, int]:
@@ -291,7 +317,10 @@
         if image_format == 'png':
             imgpath, depth = convert_dvi_to_png(dvipath, self.builder)
         elif image_format == 'svg':
-            imgpath, depth = convert_dvi_to_svg(dvipath, self.builder)
+            if self.builder.config.imgmath_pdf2svg:
+                imgpath, depth = convert_pdf_to_svg(dvipath, self.builder)
+            else:
+                imgpath, depth = convert_dvi_to_svg(dvipath, self.builder)
     except InvokeError:
         self.builder._imgmath_warned_image_translator = True  # type: ignore
         return None, None
@@ -396,8 +425,10 @@
                          ['-gamma', '1.5', '-D', '110', '-bg', 'Transparent'],
                          'html')
     app.add_config_value('imgmath_dvisvgm_args', ['--no-fonts'], 'html')
+    app.add_config_value('imgmath_pdf2svg', False, 'html')
     app.add_config_value('imgmath_latex_args', [], 'html')
     app.add_config_value('imgmath_latex_preamble', '', 'html')
+    app.add_config_value('imgmath_latex_additional_files', [], 'html')
     app.add_config_value('imgmath_add_tooltips', True, 'html')
     app.add_config_value('imgmath_font_size', 12, 'html')
     app.connect('build-finished', cleanup_tempdir)