mathjax2mobi: تحويل MathJax HTML إلى كتب إلكترونية

Home PDF

نظرة عامة على المشروع

دعونا أولاً نلقي نظرة عامة على حالة المشروع.

فاينمان_أونلاين

التغيير

لَاتِكْس

epub_black

epub_beautiful

بعد الانتهاء من المشروع، شعرت ببعض السعادة. كتبت هذه الجملة.

كتبت الكود طوال اليوم، وأخيرًا حصلت على كتاب إلكتروني جميل لمحاضرات فيزياء فاينمان! محاضرات فيزياء فاينمان متاحة على الإنترنت، وهي مكتوبة باستخدام latex الذي يتميز بعرض رائع للمعادلات الرياضية. وعندما يتم نشرها على الإنترنت، يتم استخدام مكتبة mathjax لتحويل كود latex إلى كود html، مما ينتج عنه الكثير من علامات div و span. لكن الكتب الإلكترونية لا تدعم هذه الطريقة. لذا، كانت الفكرة هي استخراج صفحات الويب، وعكس عملية عرض mathjax، ثم استبدالها بصور svg. واجهت العديد من المشاكل، أولها أن الكود يحتوي على الكثير من وحدات latex المخصصة التي يجب إضافتها؛ وثانيًا، أن تضمين الكثير من صور svg يسبب مشاكل. إذا كانت الصورة واحدة فلا مشكلة، ولكن عند وجود الكثير منها تظهر مشاكل. ربما يكون هذا بسبب بعض الأخطاء الغريبة في المتصفحات و svg. الحل كان ببساطة حفظ 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، يوجد نص latex كما هو تحت علامة script. يقوم 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

انظر إلى الكود.

    # تشغيل LaTeX وإنشاء ملف DVI
    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 غير موجود')

يبدو أن هذا يعتمد أيضًا على أمر latex.

قم بالتثبيت.

brew install --cask mactex
==> ملاحظات
يجب عليك إعادة تشغيل نافذة الطرفية الخاصة بك حتى يتم تفعيل تثبيت أدوات MacTex CLI.
بدلاً من ذلك، يمكن لمستخدمي Bash و Zsh تشغيل الأمر:
  eval "$(/usr/libexec/path_helper)"
==> جاري التحميل http://mirror.ctan.org/systems/mac/mactex/mactex-20200407.pkg
==> جاري التحميل من https://mirrors.aliyun.com/CTAN/systems/mac/mactex/mactex-20200407.pkg
######################################################################## 100.0%
تم تلبية جميع تبعيات الصيغة.
==> جاري تثبيت Cask mactex
==> جاري تشغيل المثبت لـ mactex؛ قد يلزم إدخال كلمة المرور الخاصة بك.
installer: اسم الحزمة هو MacTeX
installer: تم تطبيق ملف تغييرات الاختيارات '/private/tmp/choices20210315-4643-5884ro.xml'
installer: جاري التثبيت في المسار الأساسي /
installer: تم التثبيت بنجاح.
🍺  تم تثبيت mactex بنجاح!

تم التثبيت بنجاح.

% latex
هذا هو pdfTeX، الإصدار 3.14159265-2.6-1.40.21 (TeX Live 2020) (تنسيق مسبق التحميل=latex)
 تم تمكين \write18 المقيد.
**
out = latex2svg(r'\( e^{i \pi} + 1 = 0 \)')
print(out['depth'])
print(out['svg'])

في الكود أعلاه، يتم استخدام الدالة latex2svg لتحويل التعبير الرياضي ( e^{i \pi} + 1 = 0 ) إلى صيغة SVG. بعد ذلك، يتم طباعة عمق الصورة (depth) ورمز SVG الناتج (svg).

svg = open('1.svg', 'w')
svg.write(out['svg'])
svg.close()

يمكن الآن إنشاء ملفات svg.

لذا، جرب إنشاء نص latex الذي تحصل عليه من mathjax.

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: الأمر '['latex', '-interaction', 'nonstopmode', '-halt-on-error', 'code.tex']' أعاد حالة خروج غير صفرية 1.

أي صيغة محددة كانت خاطئة؟

\tfrac{1}{2}mv^2

لاتيكس

لاتيكس (LaTeX) هو نظام لإعداد المستندات عالية الجودة، يستخدم بشكل شائع في الأوساط الأكاديمية والعلمية لإنتاج أوراق بحثية وكتب وعروض تقديمية. يتميز لاتيكس بقدرته على التعامل مع المعادلات الرياضية المعقدة والرموز العلمية بسهولة، مما يجعله الخيار المفضل للعديد من الباحثين والطلاب.

مثال بسيط في لاتيكس

\documentclass{article}
\usepackage[utf8]{inputenc}

\title{عنوان المستند}
\author{اسم المؤلف}
\date{\today}

\begin{document}

\maketitle

\section{مقدمة}
هذا مثال بسيط لمستند لاتيكس. يمكنك كتابة النصوص وإضافة المعادلات الرياضية بسهولة.

\section{معادلة رياضية}
هنا معادلة رياضية بسيطة:

\[
E = mc^2
\]

\end{document}

في هذا المثال، يتم إنشاء مستند بسيط يحتوي على عنوان ومقدمة ومعادلة رياضية. يمكنك تعديل النصوص وإضافة المزيد من الأقسام حسب الحاجة.

لنتعلم latex.

\documentclass[12pt]{article}
\usepackage{lingmacros}
\usepackage{tree-dvips}
\begin{document}

\section*{ملاحظات حول ورقة البحث الخاصة بي}

لا تنسَ تضمين أمثلة على التخصيص الموضعي.
تبدو هكذا:

{\small \enumsentence{التركيز من خلال الموضوع الجملة:
\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 & } {جون، (من) الواضح أن ماري تحبه.}} }

\subsection*{كيفية التعامل مع التوبيكالايزيشن (التركيز على موضوع الجملة)}

سأفترض ببساطة بنية شجرة مثل (\ex{1}).

{\small \enumsentence{هيكل إسقاطات A$’$:\ [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*{المزاج}

يتغير المزاج عندما يكون هناك موضوع، وكذلك عندما يكون هناك حركة WH. \emph{Irrealis} هو المزاج عندما يكون هناك موضوع غير فاعل أو عبارة WH في Comp. \emph{Realis} هو المزاج عندما يكون هناك موضوع فاعل أو عبارة WH.

\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.

latex

لنلقِ نظرة على الكود المصدري والنتيجة المقدمة بعد التصيير، لنرى ما يمكننا تعلمه.

\begin{document}
\end{document}

هكذا يتم تغليف المستند.

\section*{ملاحظات لورقتي البحثية}

هذا يشير إلى بداية عنوان section.

\subsection*{كيفية التعامل مع التوبيكاليزيشن (التركيز على الموضوع)}

هذا يشير إلى العنوان الفرعي.

\shortex{7}{a John$_i$ [a & kltukl & [el & 
  {\bf l-}oltoir & er & ngii$_i$ & a Mary]]}

shortex

يمكن استخدام $_i$ لتمثيل الحروف السفلية. واستخدام {\bf l-} لتمثيل النص الغامق.

\enumsentence{هيكل إسقاطات A$'$:\\ [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}
}

node

لاحظ استخدام nodeconnect للإشارة إلى الاتصال بين العقد.

تحويل LaTeX إلى SVG

لتحويل معادلات LaTeX إلى صور بتنسيق SVG، يمكنك استخدام أدوات مختلفة. فيما يلي بعض الطرق الشائعة:

1. استخدام MathJax مع svg كتنسيق إخراج

يمكنك استخدام MathJax لتحويل معادلات LaTeX إلى SVG مباشرة في المتصفح. إليك مثال بسيط:

<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>

ثم يمكنك كتابة معادلات LaTeX داخل عناصر script من نوع math/tex:

<script type="math/tex">E = mc^2</script>

2. استخدام LaTeX مع dvisvgm

إذا كنت تريد تحويل ملفات LaTeX إلى SVG محليًا، يمكنك استخدام dvisvgm. أولاً، قم بتحويل ملف LaTeX إلى DVI باستخدام latex:

latex input.tex

ثم قم بتحويل ملف DVI إلى SVG باستخدام dvisvgm:

dvisvgm input.dvi

3. استخدام pandoc مع MathJax

يمكنك أيضًا استخدام pandoc لتحويل مستندات LaTeX إلى HTML مع معادلات SVG:

pandoc input.tex -o output.html --mathjax

4. استخدام MathJax-Node

إذا كنت تريد تحويل معادلات LaTeX إلى SVG برمجيًا باستخدام Node.js، يمكنك استخدام mathjax-node:

npm install mathjax-node

ثم يمكنك استخدام الكود التالي لتحويل معادلة LaTeX إلى SVG:

const mjAPI = require("mathjax-node");
mjAPI.config({
  MathJax: {
    svg: {
      fontCache: 'global'
    }
  }
});
mjAPI.start();

mjAPI.typeset({
  math: 'E = mc^2',
  format: 'TeX',
  svg: true,
}, function (data) {
  if (!data.errors) {
    console.log(data.svg);
  }
});

هذه بعض الطرق الشائعة لتحويل معادلات LaTeX إلى SVG. يمكنك اختيار الطريقة التي تناسب احتياجاتك بناءً على البيئة التي تعمل فيها.

استمرار المشروع.

\documentclass[16pt]{article}
\usepackage{amsmath}
\begin{document}

[\tfrac{1}{2}mv^2]

\end{document}

frac

هذا يمكن أن يتم عرضه بشكل صحيح. في الكود، قد لا يتم عرضه بشكل صحيح، ربما لأنه لم يتم إضافة \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}
! تم إدخال علامة $ مفقودة.
<النص المُدخل>
                $
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 + '$'
    # إذا كانت 'frac' موجودة في النص الرياضي
    # wrap = '$' + mathjax.string + '$'
    if 'FLP' in mathjax.string:
        continue
    elif 'Fig' in mathjax.string:
        continue
    elif 'eps' in mathjax.string:
        continue
    out = latex2svg(wrap)
    # طباعة الناتج
    node = BeautifulSoup(out['svg'], features="lxml")
    svg = node.find('svg')
    mathjax.insert_after(svg)
    # طباعة SVG الناتج
    # break
    # mathjax.replaceWith(out['svg'])    
    
    # طباعة خصائص mathjax
    # break
    
    # out = latex2svg(wrap)    
    # طباعة SVG الناتج
# طباعة عدد العناصر داخل soup
print(len(soup.contents))

# فتح ملف الإخراج للكتابة
output_file = open('out.html', 'w')
# كتابة المحتوى المنسق لـ soup في الملف
output_file.write(soup.prettify())
# إغلاق الملف بعد الانتهاء من الكتابة
output_file.close()

# طباعة محتويات soup (معلّق)
# print(soup.contents)
# out = latex2svg(r'\( e^{i \pi} + 1 = 0 \)')
# print(out['depth'])
# print(out['svg'])

في الكود أعلاه، يتم استخدام الدالة latex2svg لتحويل معادلة لاتيكس إلى صيغة SVG. يتم تمرير المعادلة ( e^{i \pi} + 1 = 0 ) كسلسلة نصية إلى الدالة. الناتج out هو قاموس يحتوي على معلومات حول الصورة الناتجة، بما في ذلك العمق (depth) والصيغة SVG (svg). يتم طباعة هذه القيم باستخدام print.

# svg = open('1.svg', 'w')
# svg.write(out['svg'])
# svg.close()

أنا أختبر كل هذه الأشياء لأي سبب؟

```python
    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

ملاحظة: النص الموجود في الكود هو رمز رياضي يستخدم في لغة LaTeX لتمثيل الضرب النقطي بين متجه القوة (\FLPF) ومتجه السرعة (\FLPv). لا يتم ترجمة الرموز الرياضية أو الأكواد البرمجية.

عندما حدث خطأ أثناء التحويل في الكود. أي أن latex2svg.py قد فشل. هنا يتم استخدام برنامج latex لإجراء التحويل.

\documentclass{article}
\usepackage{listings}
\usepackage{xcolor}

\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}

\lstdefinestyle{mystyle}{
    backgroundcolor=\color{backcolour},   
    commentstyle=\color{codegreen},
    keywordstyle=\color{magenta},
    numberstyle=\tiny\color{codegray},
    stringstyle=\color{codepurple},
    basicstyle=\ttfamily\footnotesize,
    breakatwhitespace=false,         
    breaklines=true,                 
    captionpos=b,                    
    keepspaces=true,                 
    numbers=left,                    
    numbersep=5pt,                  
    showspaces=false,                
    showstringspaces=false,
    showtabs=false,                  
    tabsize=2
}

\lstset{style=mystyle}

\begin{document}

\begin{lstlisting}[language=Python]
def hello_world():
    print("Hello, world!")
\end{lstlisting}

\end{document}
\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
! تسلسل تحكم غير معروف.
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}


هذا صحيح.

![fv1](assets/images/feynman/fv1.png)

### تحليل الكود

لنلقِ نظرة على الكود النهائي.

```python
import subprocess
from bs4 import BeautifulSoup
from latex2svg import latex2svg
def clean_mathjax(soup, name, cls):
    previews = soup.findAll(name, {'class': cls})
    for preview in previews:
        preview.decompose()
        
def clean_script(soup):
    scripts = soup.findAll('script')
    for s in scripts:
        s.decompose()    
def wrap_latex(mathjax, equation = False):
    wrap = ''
    if equation:
        wrap = mathjax.string
    else:
        wrap = '$' + mathjax.string + '$'
    wrap = wrap.replace('label', 'tag')
    return wrap
 
def wrap_svg(svg, equation):
    if equation:
        p = BeautifulSoup(f'<div style="text-align:center;"></div>', features="lxml")
        p.div.append(svg)
        return p.div
    else:
        return svg
def to_svg(mathjaxs, equation=False):
    if equation:
        svg_prefix = 'eq_'
    else:
        svg_prefix = 'in_'
    i = 0
    for mathjax in mathjaxs:     
        print(mathjax.string)
        wrap = wrap_latex(mathjax, equation=equation)   
        out = {}
        try:
            out = latex2svg(wrap)   
        except subprocess.CalledProcessError as err:
            raise err      
            
        f = open(f'svgs/{svg_prefix}{i}.svg', 'w')
        f.write(out['svg'])
        f.close()
        
        node = BeautifulSoup('<img>', features="lxml")
        img = node.find('img')
        img.attrs['src'] = f'./svgs/{svg_prefix}{i}.svg'
        img.attrs['style'] = 'vertical-align: middle; margin: 0.5em 0;'
        
        p = wrap_svg(img, equation)
        mathjax.insert_after(p)
        i +=1

ترجمة الكود أعلاه:

def to_svg(mathjaxs, equation=False):
    if equation:
        svg_prefix = 'eq_'
    else:
        svg_prefix = 'in_'
    i = 0
    for mathjax in mathjaxs:     
        print(mathjax.string)
        wrap = wrap_latex(mathjax, equation=equation)   
        out = {}
        try:
            out = latex2svg(wrap)   
        except subprocess.CalledProcessError as err:
            raise err      
            
        f = open(f'svgs/{svg_prefix}{i}.svg', 'w')
        f.write(out['svg'])
        f.close()
        
        node = BeautifulSoup('<img>', features="lxml")
        img = node.find('img')
        img.attrs['src'] = f'./svgs/{svg_prefix}{i}.svg'
        img.attrs['style'] = 'vertical-align: middle; margin: 0.5em 0;'
        
        p = wrap_svg(img, equation)
        mathjax.insert_after(p)
        i +=1

ملاحظة: الكود يبدو أنه يقوم بتحويل معادلات LaTeX إلى صور SVG وحفظها في ملفات، ثم إدراجها في مستند HTML باستخدام BeautifulSoup. الترجمة الحرفية للكود لا تغير من وظيفته، لذا تم ترك الكود كما هو مع شرح بسيط للوظيفة.

def main():    
    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")
    clean_mathjax(soup, 'span', 'MathJax')
    clean_mathjax(soup, 'div', 'MathJax_Display')
    clean_mathjax(soup, 'span', 'MathJax_Preview')
    
    mathjaxs = soup.findAll('script', {'type': 'math/tex'})
    to_svg(mathjaxs, equation=False)
    
    mathjaxs = soup.findAll('script', {'type': 'math/tex; mode=display'})   
    to_svg(mathjaxs, equation=True)
    
    clean_script(soup)
    
    output_file = open('out.html', 'w')
    output_file.write(soup.prettify())
    output_file.close()    

عندما نرغب في تحويل كتاب إلكتروني بالكامل، يمكننا أولاً تجربة صفحة واحدة.

    file = open('The Feynman Lectures on Physics Vol. I Ch. 13_ Work and Potential Energy (A).html')
    content = file.read()

هنا تم تنزيل صفحة واحدة.

MathJax يُنشئ الكثير من عناصر div و span. على سبيل المثال، المعادلة T+U=const يتم إنشاؤها بواسطة MathJax بهذه الطريقة.

<span class="MathJax">T</span>
<span class="MathJax">+</span>
<span class="MathJax">U</span>
<span class="MathJax">=</span>
<span class="MathJax">ثابت</span>

هذه الأشياء مزعجة وقد تؤثر على نصنا. نظرًا لأن لدينا بالفعل svg، فلا نحتاج إلى هذه الأشياء.

def clean_mathjax(soup, name, cls):
    previews = soup.findAll(name, {'class': cls})
    for preview in previews:
        preview.decompose()

تمت ترجمة الكود إلى:

def clean_mathjax(soup, name, cls):
    previews = soup.findAll(name, {'class': cls})
    for preview in previews:
        preview.decompose()

ملاحظة: الكود بقي كما هو لأنه مكتوب بلغة برمجة (Python) ولا يتم ترجمته.

clean_mathjax(soup, 'span', 'MathJax')
clean_mathjax(soup, 'div', 'MathJax_Display')
clean_mathjax(soup, 'span', 'MathJax_Preview')

قم بإزالتها جميعًا.

    mathjaxs = soup.findAll('script', {'type': 'math/tex'})
    to_svg(mathjaxs, equation=False)
    
    mathjaxs = soup.findAll('script', {'type': 'math/tex; mode=display'})   
    to_svg(mathjaxs, equation=True)

ملاحظة: الكود أعلاه مكتوب بلغة Python ويستخدم مكتبة BeautifulSoup لاستخراج عناصر النصوص الرياضية من صفحة ويب وتحويلها إلى صيغة SVG. لا يحتاج الكود إلى ترجمة حيث أن الأوامر والمتغيرات مكتوبة بالإنجليزية وهي جزء من بناء الجملة البرمجية.

لاحظ أن هناك نوعين من script هنا.

m(dv/dt)=F

ترجمة المعادلة:

تُعبر المعادلة عن القانون الثاني لنيوتن للحركة، حيث:

الصيغة الرياضية: [ m \frac{dv}{dt} = F ]

التفسير: القوة المؤثرة على جسم ما تساوي كتلة الجسم مضروبة في تسارعه.

هذا هو النموذج المضمن.

\begin{equation}
\underset{\text{الطاقة الحركية}}{\tfrac{1}{2}mv^2}+
\underset{\text{الطاقة الكامنة}}{\vphantom{\tfrac{1}{2}}mgh}=\text{ثابت},\notag

هذا هو النص بشكل فقرات.

عند استخدام الصيغة المضمنة، يجب إضافة $ أو [] حول التعبير. وإلا فقد يحدث خطأ.

\begin{document}
\begin{preview}
\tfrac{1}{2}mv^2
\end{preview}
\end{document}
! تم إدخال علامة $ مفقودة.
<النص المُدخل>
                $
l.26 \tfrac{1}{2}
                 mv^2

يجب أن يتم تغييره إلى هذا:

\begin{document}
\begin{preview}
$\tfrac{1}{2}mv^2$
\end{preview}
\end{document}

لنرى الآن كيفية تحويل latex إلى svg.

    if equation:
        svg_prefix = 'eq_'
    else:
        svg_prefix = 'in_'
% tree svgs
svgs
├── eq_0.svg
├── eq_1.svg
├── in_0.svg

هكذا يتم حفظ svg.

def wrap_latex(mathjax, equation = False):
    wrap = ''
    if equation:
        wrap = mathjax.string
    else:
        wrap = '$' + mathjax.string + '$'
    wrap = wrap.replace('label', 'tag')
    return wrap

هنا سنقوم بإجراء بعض التعديلات على كود latex. لاحظ أن label قد تحولت إلى tag.

علامة

لاحظ (Eq:I:13:14) على الجانب الأيمن. إذا كان label، فإنه لم يتم تحليله بنجاح. سيظهر هذا كـ (1). هنا نستخدم tag للإشارة إليه مؤقتًا، ولم نتعمق في البحث بعد.

ثم يتم استدعاء latex2svg.py.

        out = {}
        try:
            out = latex2svg(wrap)   
        except subprocess.CalledProcessError as err:
            raise err    

ملاحظة: الكود الموجود في الملف الأصلي مكتوب بلغة Python ولا يحتاج إلى ترجمة، حيث أن الأكواد البرمجية تبقى كما هي بغض النظر عن اللغة المستخدمة في الوثيقة.

اطلع على latex2svg.py.

    # تشغيل LaTeX وإنشاء ملف DVI
    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 غير موجود')

هنا يتم استدعاء أمر latex.

 % latex --help
الاستخدام: pdftex [خيار]... [اسم الملف[.tex]] [أوامر]
   أو: pdftex [خيار]... لسطر الأول
   أو: pdftex [خيار]... &تنسيق ARGS
  تشغيل pdfTeX على اسم الملف، عادةً ما يتم إنشاء اسم الملف.pdf.
    try:
        ret = subprocess.run(shlex.split(params['dvisvgm_cmd']+' code.dvi'),
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                             cwd=working_directory, env=env)
        ret.check_returncode()
    except FileNotFoundError:
        raise RuntimeError('لم يتم العثور على dvisvgm')

هنا يتم استدعاء أمر dvisvgm.

% dvisvgm
dvisvgm 2.9.1

هذا البرنامج يحول ملفات DVI، التي يتم إنشاؤها بواسطة TeX/LaTeX، بالإضافة إلى ملفات EPS وPDF، إلى تنسيق SVG القائم على XML والذي يعتبر تنسيقًا متجهيًا قابلًا للتحجيم.

الاستخدام: dvisvgm [خيارات] ملف_dvi dvisvgm –eps [خيارات] ملف_eps dvisvgm –pdf [خيارات] ملف_pdf


أين تكتب وحدات الماكرو المخصصة لـ `latex` التي تم ذكرها أعلاه؟ هنا يجب تعديل `latex2svg.py`. قم بتعديل `default_preamble`.

```python
default_preamble = r"""
\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}} “””


بعد التحويل بنجاح، يتم الكتابة إلى الملف.

```python
        f = open(f'svgs/{svg_prefix}{i}.svg', 'w')
        f.write(out['svg'])
        f.close()

استمر.

        node = BeautifulSoup('<img>', features="lxml")
        img = node.find('img')
        img.attrs['src'] = f'./svgs/{svg_prefix}{i}.svg'
        img.attrs['style'] = 'vertical-align: middle; margin: 0.5em 0;'

ترجمة الكود إلى العربية:

        node = BeautifulSoup('<img>', features="lxml")  # إنشاء كائن BeautifulSoup من وسم <img>
        img = node.find('img')  # البحث عن وسم <img> داخل الكائن
        img.attrs['src'] = f'./svgs/{svg_prefix}{i}.svg'  # تعيين السمة 'src' لمسار ملف SVG
        img.attrs['style'] = 'vertical-align: middle; margin: 0.5em 0;'  # تعيين السمة 'style' لتنسيق الصورة

ملاحظة: تم الحفاظ على الكود الأصلي كما هو لأنه يتضمن أسماء مكتبات ووظائف محددة (مثل BeautifulSoup) والتي لا يتم ترجمتها عادةً. تمت إضافة التعليقات باللغة العربية لشرح الكود.

هنا نقوم بإنشاء وسم img.

def wrap_svg(svg, equation):
    if equation:
        p = BeautifulSoup(f'<div style="text-align:center;"></div>', features="lxml")
        p.div.append(svg)
        return p.div
    else:
        return svg
      
p = wrap_svg(img, equation)

تمت ترجمة الكود أعلاه إلى:

def wrap_svg(svg, equation):
    if equation:
        p = BeautifulSoup(f'<div style="text-align:center;"></div>', features="lxml")
        p.div.append(svg)
        return p.div
    else:
        return svg
      
p = wrap_svg(img, equation)

ملاحظة: الكود لم يتغير لأنه يحتوي على أسماء متغيرات ودوال بالإنجليزية، ولم يتم تغييرها وفقًا للتعليمات.

إذا كانت latex عبارة عن فقرة واحدة، فقم بتغليفها باستخدام div وتوسيطها.

mathjax.insert_after(p)

هنا يتم إضافة وسم div أو وسم img بعد وسم script الأصلي.

def clean_script(soup):
    scripts = soup.findAll('script')
    for s in scripts:
        s.decompose()    
        
clean_script(soup)

ترجمة الكود إلى العربية:

def تنظيف_النصي(soup):
    النصوص = soup.findAll('script')
    for نص in النصوص:
        نص.decompose()    
        
تنظيف_النصي(soup)

في هذا الكود، يتم تعريف دالة تنظيف_النصي التي تقوم بإزالة جميع عناصر النصوص البرمجية (script) من كائن soup الذي يمثل وثيقة HTML. يتم استخدام findAll للعثور على جميع العناصر من نوع script، ثم يتم استخدام decompose لإزالة كل عنصر نصي من الوثيقة.

بعد استبدال جميع عناصر latex بـ svg، لن تكون هناك حاجة إلى script. قم بحذفها لجعل الكود أكثر نظافة.

أخيرًا، يتم كتابة html المعدل بالكامل في ملف.

    output_file = open('out.html', 'w')
    output_file.write(soup.prettify())
    output_file.close()    

ثم استخدم أداة pandoc لتحويلها إلى صيغة epub.

pandoc -s -r html out.html -o feynman.epub

سيتم فتح كتاب إلكتروني جميل.

لماذا لا يتم تضمين علامة svg مباشرة، بدلاً من استخدام img لإدراجها؟ أي بكتابة مثل هذا:

<p></p>
<svg></svg>
<p></p>

هناك bug غريب جدًا. عندما يكون هناك الكثير من ملفات svg، يحدث شيء مثل هذا.

svg_p1

لاحقًا اكتشفت أنه يمكن استخدام img لإدراج الصور. أما عن سبب ذلك، لم أفهمه تمامًا. عندما أخذت هذا الـ svg الفردي وعرضته في المتصفح، لم تكن هناك مشكلة. يبدو أن المشكلة تحدث عندما يقوم المتصفح بعرض عدد كبير جدًا من ملفات svg في نفس الوقت.

الختام

أما بالنسبة لكيفية تحويل epub إلى mobi، يمكنك استخدام الأداة الرسمية من Kindle وهي Kindle Previewer 3. لاحظ أن هذا مجرد فصل واحد.

كود المشروع موجود في feynman-lectures-mobi@lzwjava.

كيف يمكن استخراج جميع الصفحات وتنظيمها في كتاب إلكتروني؟ سنتحدث عن ذلك لاحقًا. ولكن هذا الفصل من محاضرات فينمان في الفيزياء كافٍ للقراءة. حسنًا، لنبدأ القراءة على جهاز Kindle.


Back 2025.01.18 Donate