mathjax2mobi:將MathJax HTML轉換為電子書
項目簡介
先大致講講項目情況。
做完項目後,有點開心。寫下了這樣一段話。
寫了一天代碼,終於得到了漂亮的費曼物理講義電子書!費曼物理講義公開在網上,是用latex
渲染的。人們常用latex
來寫論文,它對數學公式的渲染很棒。而公開在網上,用到了mathjax
這個庫。它把latex
源碼變成了html
代碼,生成了很多的div
和span
標籤。電子書卻不支持這種方式。這時,想法是抓取網頁,逆向mathjax
渲染,接著替換成svg
圖片。出現了不少問題,一個是源碼有很多的latex
自定義宏,需要加上;第二個是內嵌很多svg
會有問題。如果是單個svg
倒沒問題,很多的時候會出現問題。大概是瀏覽器和svg
的詭異Bug。這時只要把svg
保存為文件,用img
標籤引入進來即可。公式也分為兩種,一種是文本中間的公式,一種是單行的公式。所以,最後就得到了漂亮的電子書!
查詢的資料
這裡記錄了解決項目過程中訪問的資料。因為這是一個教程,所以向學生展示一下大概做一個項目是怎麼樣的體驗。
開始項目
費曼物理講義已經在公開在網上可以閱讀。我想在Kindle
上看它。然而因為它有挺多的數學公式。它最初的稿子應該是用latex
做的。它用mathjax
這個庫來把latex
格式的內容顯示在網頁上。
舉個例子。
<span class="MathJax_Preview" style="color: inherit; display: none;">
</span>
<div class="MathJax_Display">
<span class="MathJax MathJax_FullWidth" id="MathJax-Element-10-Frame" tabindex="0" style="">
<span class="mi" id="MathJax-Span-159" style="font-family: MathJax_Math-italic;">d<span style="display: inline-block; overflow: hidden; height: 1px; width: 0.003em;">
</span>
</span>
</div>
<script type="math/tex; mode=display" id="MathJax-Element-10">\begin{equation}
\label{Eq:I:13:3}
dT/dt = Fv.
\end{equation}
</script>
上面是截取的一段html
代碼。這一塊html
代碼中。script
標籤下是latex
的原樣文本。mathjax
把它變成很多的span
。來顯示它。
我們現在有個思路。就是把mathjax
的顯示方法改成svg
圖片。
從 GitHub 上找到一個項目tuxu/latex2svg
。
from latex2svg import latex2svg
out = latex2svg(r'\( e^{i \pi} + 1 = 0 \)')
print(out['depth'])
print(out['svg'])
試著運行,但出錯了。
raise RuntimeError('latex not found')
RuntimeError: latex not found
看看代碼。
# Run LaTeX and create DVI file
try:
ret = subprocess.run(shlex.split(params['latex_cmd']+' code.tex'),
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=working_directory)
ret.check_returncode()
except FileNotFoundError:
raise RuntimeError('latex not found')
原來這也依賴於latex
命令。
安裝一下。
brew install --cask mactex
==> Caveats
You must restart your terminal window for the installation of MacTex CLI tools to take effect.
Alternatively, Bash and Zsh users can run the command:
eval "$(/usr/libexec/path_helper)"
==> Downloading http://mirror.ctan.org/systems/mac/mactex/mactex-20200407.pkg
==> Downloading from https://mirrors.aliyun.com/CTAN/systems/mac/mactex/mactex-20200407.pkg
######################################################################## 100.0%
All formula dependencies satisfied.
==> Installing Cask mactex
==> Running installer for mactex; your password may be necessary.
installer: Package name is MacTeX
installer: choices changes file '/private/tmp/choices20210315-4643-5884ro.xml' applied
installer: Installing at base path /
installer: The install was successful.
🍺 mactex was successfully installed!
安裝成功。
% latex
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=latex)
restricted \write18 enabled.
**
out = latex2svg(r'\( e^{i \pi} + 1 = 0 \)')
print(out['depth'])
print(out['svg'])
svg = open('1.svg', 'w')
svg.write(out['svg'])
svg.close()
可以生成svg
了。
所以試試把mathjax
中得到的latex
文本都生成一下。
from bs4 import BeautifulSoup
from latex2svg import latex2svg
file = open('The Feynman Lectures on Physics Vol. I Ch. 13_ Work and Potential Energy (A).html')
content = file.read()
soup = BeautifulSoup(content)
mathjaxs = soup.findAll('script', {'type': 'math/tex'})
for mathjax in mathjaxs:
print(mathjax.string)
out = latex2svg(mathjax.string)
print(out['svg'])
可惜出錯了。
raise CalledProcessError(self.returncode, self.args, self.stdout,
subprocess.CalledProcessError: Command '['latex', '-interaction', 'nonstopmode', '-halt-on-error', 'code.tex']' returned non-zero exit status 1.
具體哪個公式錯了呢。
\tfrac{1}{2}mv^2
latex
來學習一下latex
。
\documentclass[12pt]{article}
\usepackage{lingmacros}
\usepackage{tree-dvips}
\begin{document}
\section*{Notes for My Paper}
Don't forget to include examples of topicalization.
They look like this:
{\small
\enumsentence{Topicalization from sentential subject:\\
\shortex{7}{a John$_i$ [a & kltukl & [el &
{\bf l-}oltoir & er & ngii$_i$ & a Mary]]}
{ & {\bf R-}clear & {\sc comp} &
{\bf IR}.{\sc 3s}-love & P & him & }
{John, (it's) clear that Mary loves (him).}}
}
\subsection*{How to handle topicalization}
I'll just assume a tree structure like (\ex{1}).
{\small
\enumsentence{Structure of A$'$ Projections:\\ [2ex]
\begin{tabular}[t]{cccc}
& \node{i}{CP}\\ [2ex]
\node{ii}{Spec} & &\node{iii}{C$'$}\\ [2ex]
&\node{iv}{C} & & \node{v}{SAgrP}
\end{tabular}
\nodeconnect{i}{ii}
\nodeconnect{i}{iii}
\nodeconnect{iii}{iv}
\nodeconnect{iii}{v}
}
}
\subsection*{Mood}
Mood changes when there is a topic, as well as when
there is WH-movement. \emph{Irrealis} is the mood when
there is a non-subject topic or WH-phrase in Comp.
\emph{Realis} is the mood when there is a subject topic
or WH-phrase.
\end{document}
網上找到一段樣例的latex
源碼。
% latex code.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=latex)
restricted \write18 enabled.
entering extended mode
(./code.tex
LaTeX2e <2020-02-02> patch level 5
L3 programming layer <2020-03-06>
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/article.cls
Document Class: article 2019/12/20 v1.4l Standard LaTeX document class
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size12.clo))
(/usr/local/texlive/2020/texmf-dist/tex/latex/tree-dvips/lingmacros.sty)
(/usr/local/texlive/2020/texmf-dist/tex/latex/tree-dvips/tree-dvips.sty
tree-dvips version .91 of May 16, 1995
) (/usr/local/texlive/2020/texmf-dist/tex/latex/l3backend/l3backend-dvips.def)
(./code.aux) [1] (./code.aux) )
Output written on code.dvi (1 page, 3416 bytes).
Transcript written on code.log.
來對著源碼和渲染後的效果,看看能學到什麼。
\begin{document}
\end{document}
這樣來把文檔裹起來。
\section*{Notes for My Paper}
這表示section
標題開頭。
\subsection*{How to handle topicalization}
這表示子標題。
\shortex{7}{a John$_i$ [a & kltukl & [el &
{\bf l-}oltoir & er & ngii$_i$ & a Mary]]}
可見$_i$
來表示下標。{\bf l-}
來表示加粗。
\enumsentence{Structure of A$'$ Projections:\\ [2ex]
\begin{tabular}[t]{cccc}
& \node{i}{CP}\\ [2ex]
\node{ii}{Spec} & &\node{iii}{C$'$}\\ [2ex]
&\node{iv}{C} & & \node{v}{SAgrP}
\end{tabular}
\nodeconnect{i}{ii}
\nodeconnect{i}{iii}
\nodeconnect{iii}{iv}
\nodeconnect{iii}{v}
}
注意到nodeconnect
來表示連線。
latex 轉換成 svg
繼續項目。
\documentclass[16pt]{article}
\usepackage{amsmath}
\begin{document}
\[\tfrac{1}{2}mv^2\]
\end{document}
這樣可以正確地被渲染。在代碼裡無法被渲染,可能是因為沒有加上\usepackage{amsmath}
。
\documentclass[12pt,preview]{standalone}
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{newtxtext}
\usepackage[libertine]{newtxmath}
\begin{document}
\begin{preview}
\tfrac{1}{2}mv^2
\end{preview}
\end{document}
! Missing $ inserted.
<inserted text>
$
l.12 \tfrac{1}{2}
mv^2
這樣出錯了。而改成一下這樣就可以。
\[\tfrac{1}{2}mv^2\]
進行各種試探。
from bs4 import BeautifulSoup
from latex2svg import latex2svg
file = open('The Feynman Lectures on Physics Vol. I Ch. 13_ Work and Potential Energy (A).html')
content = file.read()
soup = BeautifulSoup(content, features="lxml")
mathjaxs = soup.findAll('script', {'type': 'math/tex'})
for mathjax in mathjaxs:
print(mathjax.string)
wrap = '$' + mathjax.string + '$'
# if 'frac' in mathjax.string:
# wrap = '$' + mathjax.string + '$'
if 'FLP' in mathjax.string:
continue
elif 'Fig' in mathjax.string:
continue
elif 'eps' in mathjax.string:
continue
out = latex2svg(wrap)
# print(out)
node = BeautifulSoup(out['svg'], features="lxml")
svg = node.find('svg')
mathjax.insert_after(svg)
# print(out['svg'])
# break
# mathjax.replaceWith(out['svg'])
# print(dir(mathjax))
# break
# out = latex2svg(wrap)
# print(out['svg'])
# print(len(soup.contents))
output_file = open('out.html', 'w')
output_file.write(soup.prettify())
output_file.close()
# print(soup.contents)
# out = latex2svg(r'\( e^{i \pi} + 1 = 0 \)')
# print(out['depth'])
# print(out['svg'])
# svg = open('1.svg', 'w')
# svg.write(out['svg'])
# svg.close()
這些我都在試探什麼呢。
if 'FLP' in mathjax.string:
continue
elif 'Fig' in mathjax.string:
continue
elif 'eps' in mathjax.string:
continue
這裡當解析到有FLP
、Fig
、eps
在latex
源碼的時候,轉換的過程出錯了。
例如,在HTML中
,有這樣的腳本:
<script type="math/tex" id="MathJax-Element-11">\FLPF\cdot\FLPv</script>
解析拿到:
\FLPF\cdot\FLPv
當在代碼裡轉換的時候出錯了。也即,latex2svg.py
出錯了。這裡就是用latex
程序來轉換。
code.tex
:
\documentclass[12pt,preview]{standalone}
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{newtxtext}
\usepackage[libertine]{newtxmath}
\begin{document}
\begin{preview}
\begin{equation}
\FLPF\cdot\FLPv
\end{equation}
\end{preview}
\end{document}
$latex code.tex
! Undefined control sequence.
l.13 \FLPF
\cdot\FLPv
?
這到底是什麼問題。我後來才注意到在html
中的這段代碼。
<script type="text/x-mathjax-config;executed=true">
MathJax.Hub.Config({
TeX: {
Macros: {
FLPvec: ["\\boldsymbol{#1}", 1], Figvec: ["\\mathbf{#1}", 1], FLPC: ["\\FLPvec{C}", 0], FLPF: ["\\FLPvec{F}", 0], FLPa: ["\\FLPvec{a}", 0], FLPb: ["\\FLPvec{b}", 0], FLPr: ["\\FLPvec{r}", 0], FLPs: ["\\FLPvec{s}", 0], FLPv: ["\\FLPvec{v}", 0], ddt: ["\\frac{d#1}{d#2}", 2], epsO: ["\\epsilon_0", 0], FigC: ["\\Figvec{C}", 0]
}
}
});
</script>
這表示網頁在渲染的時候,給MathJax
設置上了宏。所以我們的latex
轉換源碼裡也應該加上。來加上它們。
\documentclass[12pt,preview]{standalone}
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{newtxtext}
\usepackage[libertine]{newtxmath}
\newcommand{\FLPvec}[1]{\boldsymbol{#1}}
\newcommand{\Figvec}[1]{\mathbf{#1}}
\newcommand{\FLPC}{\FLPvec{C}}
\newcommand{\FLPF}{\FLPvec{F}}
\newcommand{\FLPa}{\FLPvec{a}}
\newcommand{\FLPb}{\FLPvec{a}}
\newcommand{\FLPr}{\FLPvec{r}}
\newcommand{\FLPs}{\FLPvec{s}}
\newcommand{\FLPv}{\FLPvec{v}}
\newcommand{\ddt}[2]{\frac{d#1}{d#2}}
\newcommand{\epsO}{\epsilon_0}
\newcommand{\FigC}{\Figvec{C}}
\begin{document}
\begin{preview}
\begin{equation}
\FLPF\cdot\FLPv
\end{equation}
\end{preview}
\end{document}
這樣就對了。
分析代碼
來看看最後的代碼。
```python import subprocess from bs4 import BeautifulSoup from latex2svg import latex2svg
def clean_mathjax(soup, name, cls): previews