aboutsummaryrefslogtreecommitdiff
path: root/plugins/render_math
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@chimrod.com>2021-01-03 20:10:08 +0100
committerSébastien Dailly <sebastien@chimrod.com>2021-01-04 10:50:59 +0100
commit0c09a00a0b298cbd3bbd0082cc1026e22db9b1c5 (patch)
tree90a346ea34bffbd882a727c5dfd25f6c2ab54841 /plugins/render_math
parent536bb26052fa4ada16d9d09a6cc140a4d3293af8 (diff)
New article, and blog application
Diffstat (limited to 'plugins/render_math')
-rwxr-xr-xplugins/render_math/Readme.md167
-rwxr-xr-xplugins/render_math/__init__.py1
-rwxr-xr-xplugins/render_math/math.py367
-rwxr-xr-xplugins/render_math/mathjax_script_template61
-rwxr-xr-xplugins/render_math/pelican_mathjax_markdown_extension.py158
-rwxr-xr-xplugins/render_math/requirements.txt1
-rwxr-xr-xplugins/render_math/test_data/article.ipynb42
-rwxr-xr-xplugins/render_math/test_data/article.nbdata5
-rwxr-xr-xplugins/render_math/test_data/article_with_math_formulas.rst20
-rwxr-xr-xplugins/render_math/test_render_math.py56
10 files changed, 878 insertions, 0 deletions
diff --git a/plugins/render_math/Readme.md b/plugins/render_math/Readme.md
new file mode 100755
index 0000000..7d541aa
--- /dev/null
+++ b/plugins/render_math/Readme.md
@@ -0,0 +1,167 @@
+Math Render Plugin For Pelican
+==============================
+
+**NOTE: [This plugin has been moved to its own repository](https://github.com/pelican-plugins/render-math). Please file any issues/PRs there. Once all plugins have been migrated to the [new Pelican Plugins organization](https://github.com/pelican-plugins), this monolithic repository will be archived.**
+
+This plugin gives pelican the ability to render mathematics. It accomplishes
+this by using the [MathJax](http://www.mathjax.org/) javascript engine.
+
+The plugin also ensures that Typogrify and recognized math "play" nicely together, by
+ensuring [Typogrify](https://github.com/mintchaos/typogrify) does not alter math content.
+
+Both Markdown and reStructuredText is supported.
+
+Requirements
+------------
+
+ * Pelican version *3.6* or above is required.
+ * Typogrify version *2.0.7* or higher is needed for Typogrify to play
+ "nicely" with this plugin. If this version is not available, Typogrify
+ will be disabled for the entire site.
+ * BeautifulSoup4 is required to correct summaries. If BeautifulSoup4 is
+ not installed, summary processing will be ignored, even if specified
+ in user settings.
+
+Installation
+------------
+To enable, ensure that `render_math` plugin is accessible.
+Then add the following to settings.py:
+
+ PLUGINS = ["render_math"]
+
+Your site is now capable of rendering math math using the mathjax JavaScript
+engine. No alterations to the template is needed, just use and enjoy!
+
+However, if you wish, you can set the `auto_insert` setting to `False` which
+will disable the mathjax script from being automatically inserted into the
+content. You would only want to do this if you had control over the template
+and wanted to insert the script manually.
+
+### Typogrify
+In the past, using [Typgogrify](https://github.com/mintchaos/typogrify) would
+alter the math contents resulting in math that could not be rendered by MathJax.
+The only option was to ensure that Typogrify was disabled in the settings.
+
+The problem has been rectified in this plugin, but it requires at a minimum
+[Typogrify version 2.0.7](https://pypi.python.org/pypi/typogrify) (or higher).
+If this version is not present, the plugin will disable Typogrify for the entire
+site.
+
+### BeautifulSoup4
+Pelican creates summaries by truncating the contents to a specified user length.
+The truncation process is oblivious to any math and can therefore destroy
+the math output in the summary.
+
+To restore math, [BeautifulSoup4](https://pypi.python.org/pypi/beautifulsoup4/4.4.0)
+is used. If it is not installed, no summary processing will happen.
+
+Usage
+-----
+### Templates
+No alteration is needed to a template for this plugin to work. Just install
+the plugin and start writing your Math.
+
+### Settings
+Certain MathJax rendering options can be set. These options
+are in a dictionary variable called `MATH_JAX` in the pelican
+settings file.
+
+The dictionary can be set with the following keys:
+
+ * `align`: [string] controls how displayed math will be aligned. Can be set to either
+`'left'`, `'right'` or `'center'`. **Default Value**: `'center'`.
+ * `auto_insert`: [boolean] will insert the mathjax script into content that it is
+detected to have math in it. Setting it to false is not recommended.
+**Default Value**: `True`
+ * `indent`: [string] if `align` not set to `'center'`, then this controls the indent
+level. **Default Value**: `'0em'`.
+ * `show_menu`: [boolean] controls whether the mathjax contextual menu is shown.
+**Default Value**: `True`
+ * `process_escapes`: [boolean] controls whether mathjax processes escape sequences.
+**Default Value**: `True`
+ * `mathjax_font`: [string] will force mathjax to use the chosen font. Current choices
+for the font is `sanserif`, `typewriter` or `fraktur`. If this is not set, it will
+use the default font settings. **Default Value**: `default`
+ * `latex_preview`: [string] controls the preview message users are shown while mathjax is
+rendering LaTex. If set to `'Tex'`, then the TeX code is used as the preview
+(which will be visible until it is processed by MathJax). **Default Value**: `'Tex'`
+ * `color`: [string] controls the color of the mathjax rendered font. **Default Value**: `'inherit'`
+ * `linebreak_automatic`: [boolean] If set, Mathjax will try to *intelligently* break up displayed math
+(Note: It will not work for inline math). This is very useful for a responsive site. It
+is turned off by default due to it potentially being CPU expensive. **Default Value**: `False`
+ * `tex_extensions`: [list] a list of [latex extensions](http://docs.mathjax.org/en/latest/tex.html#tex-and-latex-extensions)
+accepted by mathjax. **Default Value**: `[]` (empty list)
+ * `responsive`: [boolean] tries to make displayed math render responsively. It does by determining if the width
+is less than `responsive_break` (see below) and if so, sets `align` to `left`, `indent` to `0em` and `linebreak_automatic` to `True`.
+**Default Value**: `False` (defaults to `False` for backward compatibility)
+ * `responsive_break`: [integer] a number (in pixels) representing the width breakpoint that is used
+when setting `responsive_align` to `True`. **Default Value**: 768
+ * `process_summary`: [boolean] ensures math will render in summaries and fixes math in that were cut off.
+Requires [BeautifulSoup4](http://www.crummy.com/software/BeautifulSoup/bs4/doc/) be installed. **Default Value**: `True`
+ * `message_style`: [string] This value controls the verbosity of the messages in the lower left-hand corner. Set it to `None` to eliminate all messages.
+**Default Value**: normal
+
+#### Settings Examples
+Make math render in blue and displaymath align to the left:
+
+ MATH_JAX = {'color':'blue','align':left}
+
+Use the [color](http://docs.mathjax.org/en/latest/tex.html#color) and
+[mhchem](http://docs.mathjax.org/en/latest/tex.html#mhchem) extensions:
+
+ MATH_JAX = {'tex_extensions': ['color.js','mhchem.js']}
+
+#### Resulting HTML
+Inlined math is wrapped in `span` tags, while displayed math is wrapped in `div` tags.
+These tags will have a class attribute that is set to `math` which
+can be used by template designers to alter the display of the math.
+
+Markdown
+--------
+This plugin implements a custom extension for markdown resulting in math
+being a "first class citizen" for Pelican.
+
+### Inlined Math
+Math between `$`..`$`, for example, `$`x^2`$`, will be rendered inline
+with respect to the current html block. Note: To use inline math, there
+must *not* be any whitespace before the ending `$`. So for example:
+
+ * **Relevant inline math**: `$e=mc^2$`
+ * **Will not render as inline math**: `$40 vs $50`
+
+### Displayed Math
+Math between `$$`..`$$` will be rendered "block style", for example, `$$`x^2`$$`, will be rendered centered in a
+new paragraph.
+
+#### Other Latex Display Math commands
+The other LaTeX commands which usually invoke display math mode from text mode
+are supported,
+and are automatically treated like `$$`-style displayed math
+in that they are rendered "block" style on their own lines.
+For example, `\begin{equation}` x^2 `\end{equation}`,
+will be rendered in its own block with a right justified equation number
+at the top of the block. This equation number can be referenced in the document.
+To do this, use a `label` inside of the equation format and then refer to that label
+using `ref`. For example: `\begin{equation}` `\label{eq}` X^2 `\end{equation}`.
+Now refer to that equation number by `$`\ref{eq}`$`.
+
+reStructuredText
+----------------
+If there is math detected in reStructuredText document, the plugin will automatically
+set the [math_output](http://docutils.sourceforge.net/docs/user/config.html#math-output) configuration setting to `MathJax`.
+
+### Inlined Math
+Inlined math needs to use the [math role](http://docutils.sourceforge.net/docs/ref/rst/roles.html#math):
+
+```
+The area of a circle is :math:`A_\text{c} = (\pi/4) d^2`.
+```
+
+### Displayed Math
+Displayed math uses the [math block](http://docutils.sourceforge.net/docs/ref/rst/directives.html#math):
+
+```
+.. math::
+
+ α_t(i) = P(O_1, O_2, … O_t, q_t = S_i λ)
+```
diff --git a/plugins/render_math/__init__.py b/plugins/render_math/__init__.py
new file mode 100755
index 0000000..2ac15dd
--- /dev/null
+++ b/plugins/render_math/__init__.py
@@ -0,0 +1 @@
+from .math import *
diff --git a/plugins/render_math/math.py b/plugins/render_math/math.py
new file mode 100755
index 0000000..165d59e
--- /dev/null
+++ b/plugins/render_math/math.py
@@ -0,0 +1,367 @@
+# -*- coding: utf-8 -*-
+"""
+Math Render Plugin for Pelican
+==============================
+This plugin allows your site to render Math. It uses
+the MathJax JavaScript engine.
+
+For markdown, the plugin works by creating a Markdown
+extension which is used during the markdown compilation
+stage. Math therefore gets treated like a "first class
+citizen" in Pelican
+
+For reStructuredText, the plugin instructs the rst engine
+to output Mathjax for all math.
+
+The mathjax script is by default automatically inserted
+into the HTML.
+
+Typogrify Compatibility
+-----------------------
+This plugin now plays nicely with Typogrify, but it
+requires Typogrify version 2.07 or above.
+
+User Settings
+-------------
+Users are also able to pass a dictionary of settings
+in the settings file which will control how the MathJax
+library renders things. This could be very useful for
+template builders that want to adjust the look and feel of
+the math. See README for more details.
+"""
+
+import os
+import sys
+
+from pelican import signals, generators
+
+try:
+ from bs4 import BeautifulSoup
+except ImportError as e:
+ BeautifulSoup = None
+
+try:
+ from . pelican_mathjax_markdown_extension import PelicanMathJaxExtension
+except ImportError as e:
+ PelicanMathJaxExtension = None
+
+try:
+ string_type = basestring
+except NameError:
+ string_type = str
+
+
+def process_settings(pelicanobj):
+ """Sets user specified MathJax settings (see README for more details)"""
+
+ mathjax_settings = {}
+
+ # NOTE TO FUTURE DEVELOPERS: Look at the README and what is happening in
+ # this function if any additional changes to the mathjax settings need to
+ # be incorporated. Also, please inline comment what the variables
+ # will be used for
+
+ # Default settings
+ mathjax_settings['auto_insert'] = True # if set to true, it will insert mathjax script automatically into content without needing to alter the template.
+ mathjax_settings['align'] = 'center' # controls alignment of of displayed equations (values can be: left, right, center)
+ mathjax_settings['indent'] = '0em' # if above is not set to 'center', then this setting acts as an indent
+ mathjax_settings['show_menu'] = 'true' # controls whether to attach mathjax contextual menu
+ mathjax_settings['process_escapes'] = 'true' # controls whether escapes are processed
+ mathjax_settings['latex_preview'] = 'TeX' # controls what user sees while waiting for LaTex to render
+ mathjax_settings['color'] = 'inherit' # controls color math is rendered in
+ mathjax_settings['linebreak_automatic'] = 'false' # Set to false by default for performance reasons (see http://docs.mathjax.org/en/latest/output.html#automatic-line-breaking)
+ mathjax_settings['tex_extensions'] = '' # latex extensions that can be embedded inside mathjax (see http://docs.mathjax.org/en/latest/tex.html#tex-and-latex-extensions)
+ mathjax_settings['responsive'] = 'false' # Tries to make displayed math responsive
+ mathjax_settings['responsive_break'] = '768' # The break point at which it math is responsively aligned (in pixels)
+ mathjax_settings['mathjax_font'] = 'default' # forces mathjax to use the specified font.
+ mathjax_settings['process_summary'] = BeautifulSoup is not None # will fix up summaries if math is cut off. Requires beautiful soup
+ mathjax_settings['message_style'] = 'normal' # This value controls the verbosity of the messages in the lower left-hand corner. Set it to "none" to eliminate all messages
+ mathjax_settings['font_list'] = ['STIX', 'TeX'] # Include in order of preference among TeX, STIX-Web, Asana-Math, Neo-Euler, Gyre-Pagella, Gyre-Termes and Latin-Modern
+ mathjax_settings['equation_numbering'] = 'none' # AMS, auto, none
+
+ # Source for MathJax
+ mathjax_settings['source'] = "'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'"
+
+ # Get the user specified settings
+ try:
+ settings = pelicanobj.settings['MATH_JAX']
+ except:
+ settings = None
+
+ # If no settings have been specified, then return the defaults
+ if not isinstance(settings, dict):
+ return mathjax_settings
+
+ # The following mathjax settings can be set via the settings dictionary
+ for key, value in ((key, settings[key]) for key in settings):
+ # Iterate over dictionary in a way that is compatible with both version 2
+ # and 3 of python
+
+ if key == 'align':
+ typeVal = isinstance(value, string_type)
+
+ if not typeVal:
+ continue
+
+ if value == 'left' or value == 'right' or value == 'center':
+ mathjax_settings[key] = value
+ else:
+ mathjax_settings[key] = 'center'
+
+ if key == 'indent':
+ mathjax_settings[key] = value
+
+ if key == 'source':
+ mathjax_settings[key] = value
+
+ if key == 'show_menu' and isinstance(value, bool):
+ mathjax_settings[key] = 'true' if value else 'false'
+
+ if key == 'message_style':
+ mathjax_settings[key] = value if value is not None else 'none'
+
+ if key == 'auto_insert' and isinstance(value, bool):
+ mathjax_settings[key] = value
+
+ if key == 'process_escapes' and isinstance(value, bool):
+ mathjax_settings[key] = 'true' if value else 'false'
+
+ if key == 'latex_preview':
+ typeVal = isinstance(value, string_type)
+
+ if not typeVal:
+ continue
+
+ mathjax_settings[key] = value
+
+ if key == 'color':
+ typeVal = isinstance(value, string_type)
+
+ if not typeVal:
+ continue
+
+ mathjax_settings[key] = value
+
+ if key == 'linebreak_automatic' and isinstance(value, bool):
+ mathjax_settings[key] = 'true' if value else 'false'
+
+ if key == 'process_summary' and isinstance(value, bool):
+ if value and BeautifulSoup is None:
+ print("BeautifulSoup4 is needed for summaries to be processed by render_math\nPlease install it")
+ value = False
+
+ mathjax_settings[key] = value
+
+ if key == 'responsive' and isinstance(value, bool):
+ mathjax_settings[key] = 'true' if value else 'false'
+
+ if key == 'responsive_break' and isinstance(value, int):
+ mathjax_settings[key] = str(value)
+
+ if key == 'tex_extensions' and isinstance(value, list):
+ # filter string values, then add '' to them
+ value = filter(lambda string: isinstance(string, string_type), value)
+ value = map(lambda string: "'%s'" % string, value)
+ mathjax_settings[key] = ',' + ','.join(value)
+
+ if key == 'mathjax_font':
+ typeVal = isinstance(value, string_type)
+
+ if not typeVal:
+ continue
+
+ value = value.lower()
+
+ if value == 'sanserif':
+ value = 'SansSerif'
+ elif value == 'fraktur':
+ value = 'Fraktur'
+ elif value == 'typewriter':
+ value = 'Typewriter'
+ else:
+ value = 'default'
+
+ mathjax_settings[key] = value
+
+ if key == 'font_list' and isinstance(value, list):
+ # make an array string from the list
+ value = filter(lambda string: isinstance(string, string_type), value)
+ value = map(lambda string: ",'%s'" % string, value)
+ mathjax_settings[key] = ''.join(value)[1:]
+
+ if key == 'equation_numbering':
+ mathjax_settings[key] = value if value is not None else 'none'
+
+ return mathjax_settings
+
+def process_summary(article):
+ """Ensures summaries are not cut off. Also inserts
+ mathjax script so that math will be rendered"""
+
+ summary = article.summary
+ summary_parsed = BeautifulSoup(summary, 'html.parser')
+ math = summary_parsed.find_all(class_='math')
+
+ if len(math) > 0:
+ last_math_text = math[-1].get_text()
+ if len(last_math_text) > 3 and last_math_text[-3:] == '...':
+ content_parsed = BeautifulSoup(article._content, 'html.parser')
+ full_text = content_parsed.find_all(class_='math')[len(math)-1].get_text()
+ math[-1].string = "%s ..." % full_text
+ summary = summary_parsed.decode()
+
+ # clear memoization cache
+ import functools
+ if isinstance(article.get_summary, functools.partial):
+ memoize_instance = article.get_summary.func.__self__
+ memoize_instance.cache.clear()
+
+ article._summary = "%s<script type='text/javascript'>%s</script>" % (summary, process_summary.mathjax_script)
+
+def configure_typogrify(pelicanobj, mathjax_settings):
+ """Instructs Typogrify to ignore math tags - which allows Typogrify
+ to play nicely with math related content"""
+
+ # If Typogrify is not being used, then just exit
+ if not pelicanobj.settings.get('TYPOGRIFY', False):
+ return
+
+ try:
+ import typogrify
+ from distutils.version import LooseVersion
+
+ if LooseVersion(typogrify.__version__) < LooseVersion('2.0.7'):
+ raise TypeError('Incorrect version of Typogrify')
+
+ from typogrify.filters import typogrify
+
+ # At this point, we are happy to use Typogrify, meaning
+ # it is installed and it is a recent enough version
+ # that can be used to ignore all math
+ # Instantiate markdown extension and append it to the current extensions
+ pelicanobj.settings['TYPOGRIFY_IGNORE_TAGS'].extend(['.math', 'script']) # ignore math class and script
+
+ except (ImportError, TypeError) as e:
+ pelicanobj.settings['TYPOGRIFY'] = False # disable Typogrify
+
+ if isinstance(e, ImportError):
+ print("\nTypogrify is not installed, so it is being ignored.\nIf you want to use it, please install via: pip install typogrify\n")
+
+ if isinstance(e, TypeError):
+ print("\nA more recent version of Typogrify is needed for the render_math module.\nPlease upgrade Typogrify to the latest version (anything equal or above version 2.0.7 is okay).\nTypogrify will be turned off due to this reason.\n")
+
+def process_mathjax_script(mathjax_settings):
+ """Load the mathjax script template from file, and render with the settings"""
+
+ # Read the mathjax javascript template from file
+ with open (os.path.dirname(os.path.realpath(__file__))
+ + '/mathjax_script_template', 'r') as mathjax_script_template:
+ mathjax_template = mathjax_script_template.read()
+
+ return mathjax_template.format(**mathjax_settings)
+
+def mathjax_for_markdown(pelicanobj, mathjax_script, mathjax_settings):
+ """Instantiates a customized markdown extension for handling mathjax
+ related content"""
+
+ # Create the configuration for the markdown template
+ config = {}
+ config['mathjax_script'] = mathjax_script
+ config['math_tag_class'] = 'math'
+ config['auto_insert'] = mathjax_settings['auto_insert']
+
+ # Instantiate markdown extension and append it to the current extensions
+ try:
+ if isinstance(pelicanobj.settings.get('MD_EXTENSIONS'), list): # pelican 3.6.3 and earlier
+ pelicanobj.settings['MD_EXTENSIONS'].append(PelicanMathJaxExtension(config))
+ else:
+ pelicanobj.settings['MARKDOWN'].setdefault('extensions', []).append(PelicanMathJaxExtension(config))
+ except:
+ sys.excepthook(*sys.exc_info())
+ sys.stderr.write("\nError - the pelican mathjax markdown extension failed to configure. MathJax is non-functional.\n")
+ sys.stderr.flush()
+
+def mathjax_for_rst(pelicanobj, mathjax_script, mathjax_settings):
+ """Setup math for RST"""
+ docutils_settings = pelicanobj.settings.get('DOCUTILS_SETTINGS', {})
+ docutils_settings.setdefault('math_output', 'MathJax %s' % mathjax_settings['source'])
+ pelicanobj.settings['DOCUTILS_SETTINGS'] = docutils_settings
+ rst_add_mathjax.mathjax_script = mathjax_script
+
+def pelican_init(pelicanobj):
+ """
+ Loads the mathjax script according to the settings.
+ Instantiate the Python markdown extension, passing in the mathjax
+ script as config parameter.
+ """
+
+ # Process settings, and set global var
+ mathjax_settings = process_settings(pelicanobj)
+
+ # Generate mathjax script
+ mathjax_script = process_mathjax_script(mathjax_settings)
+
+ # Configure Typogrify
+ configure_typogrify(pelicanobj, mathjax_settings)
+
+ # Configure Mathjax For Markdown
+ if PelicanMathJaxExtension:
+ mathjax_for_markdown(pelicanobj, mathjax_script, mathjax_settings)
+
+ # Configure Mathjax For RST
+ mathjax_for_rst(pelicanobj, mathjax_script, mathjax_settings)
+
+ # Set process_summary's mathjax_script variable
+ process_summary.mathjax_script = None
+ if mathjax_settings['process_summary']:
+ process_summary.mathjax_script = mathjax_script
+
+def rst_add_mathjax(content):
+ """Adds mathjax script for reStructuredText"""
+
+ # .rst is the only valid extension for reStructuredText files
+ _, ext = os.path.splitext(os.path.basename(content.source_path))
+ if ext != '.rst':
+ return
+
+ # If math class is present in text, add the javascript
+ # note that RST hardwires mathjax to be class "math"
+ if 'class="math"' in content._content:
+ content._content += "<script type='text/javascript'>%s</script>" % rst_add_mathjax.mathjax_script
+
+def process_rst_and_summaries(content_generators):
+ """
+ Ensure mathjax script is applied to RST and summaries are
+ corrected if specified in user settings.
+
+ Handles content attached to ArticleGenerator and PageGenerator objects,
+ since the plugin doesn't know how to handle other Generator types.
+
+ For reStructuredText content, examine both articles and pages.
+ If article or page is reStructuredText and there is math present,
+ append the mathjax script.
+
+ Also process summaries if present (only applies to articles)
+ and user wants summaries processed (via user settings)
+ """
+
+ for generator in content_generators:
+ if isinstance(generator, generators.ArticlesGenerator):
+ for article in (
+ generator.articles +
+ generator.translations +
+ generator.drafts):
+ rst_add_mathjax(article)
+ #optionally fix truncated formulae in summaries.
+ if process_summary.mathjax_script is not None:
+ process_summary(article)
+ elif isinstance(generator, generators.PagesGenerator):
+ for page in generator.pages:
+ rst_add_mathjax(page)
+ for page in generator.hidden_pages:
+ rst_add_mathjax(page)
+
+def register():
+ """Plugin registration"""
+ signals.initialized.connect(pelican_init)
+ signals.all_generators_finalized.connect(process_rst_and_summaries)
diff --git a/plugins/render_math/mathjax_script_template b/plugins/render_math/mathjax_script_template
new file mode 100755
index 0000000..db8aeba
--- /dev/null
+++ b/plugins/render_math/mathjax_script_template
@@ -0,0 +1,61 @@
+if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {{
+ var align = "{align}",
+ indent = "{indent}",
+ linebreak = "{linebreak_automatic}";
+
+ if ({responsive}) {{
+ align = (screen.width < {responsive_break}) ? "left" : align;
+ indent = (screen.width < {responsive_break}) ? "0em" : indent;
+ linebreak = (screen.width < {responsive_break}) ? 'true' : linebreak;
+ }}
+
+ var mathjaxscript = document.createElement('script');
+ mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
+ mathjaxscript.type = 'text/javascript';
+ mathjaxscript.src = {source};
+
+ var configscript = document.createElement('script');
+ configscript.type = 'text/x-mathjax-config';
+ configscript[(window.opera ? "innerHTML" : "text")] =
+ "MathJax.Hub.Config({{" +
+ " config: ['MMLorHTML.js']," +
+ " TeX: {{ extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'{tex_extensions}], equationNumbers: {{ autoNumber: '{equation_numbering}' }} }}," +
+ " jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
+ " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
+ " displayAlign: '"+ align +"'," +
+ " displayIndent: '"+ indent +"'," +
+ " showMathMenu: {show_menu}," +
+ " messageStyle: '{message_style}'," +
+ " tex2jax: {{ " +
+ " inlineMath: [ ['\\\\(','\\\\)'] ], " +
+ " displayMath: [ ['$$','$$'] ]," +
+ " processEscapes: {process_escapes}," +
+ " preview: '{latex_preview}'," +
+ " }}, " +
+ " 'HTML-CSS': {{ " +
+ " availableFonts: {font_list}," +
+ " preferredFont: 'STIX'," +
+ " styles: {{ '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {{color: '{color} ! important'}} }}," +
+ " linebreaks: {{ automatic: "+ linebreak +", width: '90% container' }}," +
+ " }}, " +
+ "}}); " +
+ "if ('{mathjax_font}' !== 'default') {{" +
+ "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {{" +
+ "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
+ "VARIANT['normal'].fonts.unshift('MathJax_{mathjax_font}');" +
+ "VARIANT['bold'].fonts.unshift('MathJax_{mathjax_font}-bold');" +
+ "VARIANT['italic'].fonts.unshift('MathJax_{mathjax_font}-italic');" +
+ "VARIANT['-tex-mathit'].fonts.unshift('MathJax_{mathjax_font}-italic');" +
+ "}});" +
+ "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {{" +
+ "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
+ "VARIANT['normal'].fonts.unshift('MathJax_{mathjax_font}');" +
+ "VARIANT['bold'].fonts.unshift('MathJax_{mathjax_font}-bold');" +
+ "VARIANT['italic'].fonts.unshift('MathJax_{mathjax_font}-italic');" +
+ "VARIANT['-tex-mathit'].fonts.unshift('MathJax_{mathjax_font}-italic');" +
+ "}});" +
+ "}}";
+
+ (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
+ (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
+}}
diff --git a/plugins/render_math/pelican_mathjax_markdown_extension.py b/plugins/render_math/pelican_mathjax_markdown_extension.py
new file mode 100755
index 0000000..e739363
--- /dev/null
+++ b/plugins/render_math/pelican_mathjax_markdown_extension.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+"""
+Pelican Mathjax Markdown Extension
+==================================
+An extension for the Python Markdown module that enables
+the Pelican python blog to process mathjax. This extension
+gives Pelican the ability to use Mathjax as a "first class
+citizen" of the blog
+"""
+
+import markdown
+
+from markdown.util import etree
+from markdown.util import AtomicString
+
+class PelicanMathJaxPattern(markdown.inlinepatterns.Pattern):
+ """Inline markdown processing that matches mathjax"""
+
+ def __init__(self, pelican_mathjax_extension, tag, pattern):
+ super(PelicanMathJaxPattern,self).__init__(pattern)
+ self.math_tag_class = pelican_mathjax_extension.getConfig('math_tag_class')
+ self.pelican_mathjax_extension = pelican_mathjax_extension
+ self.tag = tag
+
+ def handleMatch(self, m):
+ node = markdown.util.etree.Element(self.tag)
+ node.set('class', self.math_tag_class)
+
+ prefix = '\\(' if m.group('prefix') == '$' else m.group('prefix')
+ suffix = '\\)' if m.group('suffix') == '$' else m.group('suffix')
+ node.text = markdown.util.AtomicString(prefix + m.group('math') + suffix)
+
+ # If mathjax was successfully matched, then JavaScript needs to be added
+ # for rendering. The boolean below indicates this
+ self.pelican_mathjax_extension.mathjax_needed = True
+ return node
+
+class PelicanMathJaxCorrectDisplayMath(markdown.treeprocessors.Treeprocessor):
+ """Corrects invalid html that results from a <div> being put inside
+ a <p> for displayed math"""
+
+ def __init__(self, pelican_mathjax_extension):
+ self.pelican_mathjax_extension = pelican_mathjax_extension
+
+ def correct_html(self, root, children, div_math, insert_idx, text):
+ """Separates out <div class="math"> from the parent tag <p>. Anything
+ in between is put into its own parent tag of <p>"""
+
+ current_idx = 0
+
+ for idx in div_math:
+ el = markdown.util.etree.Element('p')
+ el.text = text
+ el.extend(children[current_idx:idx])
+
+ # Test to ensure that empty <p> is not inserted
+ if len(el) != 0 or (el.text and not el.text.isspace()):
+ root.insert(insert_idx, el)
+ insert_idx += 1
+
+ text = children[idx].tail
+ children[idx].tail = None
+ root.insert(insert_idx, children[idx])
+ insert_idx += 1
+ current_idx = idx+1
+
+ el = markdown.util.etree.Element('p')
+ el.text = text
+ el.extend(children[current_idx:])
+
+ if len(el) != 0 or (el.text and not el.text.isspace()):
+ root.insert(insert_idx, el)
+
+ def run(self, root):
+ """Searches for <div class="math"> that are children in <p> tags and corrects
+ the invalid HTML that results"""
+
+ math_tag_class = self.pelican_mathjax_extension.getConfig('math_tag_class')
+
+ for parent in root:
+ div_math = []
+ children = list(parent)
+
+ for div in parent.findall('div'):
+ if div.get('class') == math_tag_class:
+ div_math.append(children.index(div))
+
+ # Do not process further if no displayed math has been found
+ if not div_math:
+ continue
+
+ insert_idx = list(root).index(parent)
+ self.correct_html(root, children, div_math, insert_idx, parent.text)
+ root.remove(parent) # Parent must be removed last for correct insertion index
+
+ return root
+
+class PelicanMathJaxAddJavaScript(markdown.treeprocessors.Treeprocessor):
+ """Tree Processor for adding Mathjax JavaScript to the blog"""
+
+ def __init__(self, pelican_mathjax_extension):
+ self.pelican_mathjax_extension = pelican_mathjax_extension
+
+ def run(self, root):
+ # If no mathjax was present, then exit
+ if (not self.pelican_mathjax_extension.mathjax_needed):
+ return root
+
+ # Add the mathjax script to the html document
+ mathjax_script = etree.Element('script')
+ mathjax_script.set('type','text/javascript')
+ mathjax_script.text = AtomicString(self.pelican_mathjax_extension.getConfig('mathjax_script'))
+ root.append(mathjax_script)
+
+ # Reset the boolean switch to false so that script is only added
+ # to other pages if needed
+ self.pelican_mathjax_extension.mathjax_needed = False
+ return root
+
+class PelicanMathJaxExtension(markdown.Extension):
+ """A markdown extension enabling mathjax processing in Markdown for Pelican"""
+ def __init__(self, config):
+
+ try:
+ # Needed for markdown versions >= 2.5
+ self.config['mathjax_script'] = ['', 'Mathjax JavaScript script']
+ self.config['math_tag_class'] = ['math', 'The class of the tag in which mathematics is wrapped']
+ self.config['auto_insert'] = [True, 'Determines if mathjax script is automatically inserted into content']
+ super(PelicanMathJaxExtension,self).__init__(**config)
+ except AttributeError:
+ # Markdown versions < 2.5
+ config['mathjax_script'] = [config['mathjax_script'], 'Mathjax JavaScript script']
+ config['math_tag_class'] = [config['math_tag_class'], 'The class of the tag in which mathematic is wrapped']
+ config['auto_insert'] = [config['auto_insert'], 'Determines if mathjax script is automatically inserted into content']
+ super(PelicanMathJaxExtension,self).__init__(config)
+
+ # Used as a flag to determine if javascript
+ # needs to be injected into a document
+ self.mathjax_needed = False
+
+ def extendMarkdown(self, md):
+ # Regex to detect mathjax
+ mathjax_inline_regex = r'(?P<prefix>\$)(?P<math>.+?)(?P<suffix>(?<!\s)\2)'
+ mathjax_display_regex = r'(?P<prefix>\$\$|\\begin\{(.+?)\})(?P<math>.+?)(?P<suffix>\2|\\end\{\3\})'
+
+ # Process mathjax before escapes are processed since escape processing will
+ # intefer with mathjax. The order in which the displayed and inlined math
+ # is registered below matters: we should have higher priority than 'escape' which has 180
+ md.inlinePatterns.register(PelicanMathJaxPattern(self, 'div', mathjax_display_regex), 'mathjax_displayed', 186)
+ md.inlinePatterns.register(PelicanMathJaxPattern(self, 'span', mathjax_inline_regex), 'mathjax_inlined', 185)
+
+ # Correct the invalid HTML that results from teh displayed math (<div> tag within a <p> tag)
+ md.treeprocessors.register(PelicanMathJaxCorrectDisplayMath(self), 'mathjax_correctdisplayedmath', 15)
+
+ # If necessary, add the JavaScript Mathjax library to the document. This must
+ # be last in the ordered dict (hence it is given the position '_end')
+ if self.getConfig('auto_insert'):
+ md.treeprocessors.register(PelicanMathJaxAddJavaScript(self), 'mathjax_addjavascript', 0)
diff --git a/plugins/render_math/requirements.txt b/plugins/render_math/requirements.txt
new file mode 100755
index 0000000..be64ec9
--- /dev/null
+++ b/plugins/render_math/requirements.txt
@@ -0,0 +1 @@
+typogrify
diff --git a/plugins/render_math/test_data/article.ipynb b/plugins/render_math/test_data/article.ipynb
new file mode 100755
index 0000000..890f8be
--- /dev/null
+++ b/plugins/render_math/test_data/article.ipynb
@@ -0,0 +1,42 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "The formula is:\n",
+ "\n",
+ "\\begin{align*}A =\n",
+ "LL^{T}\n",
+ "\\end{align*}\n"
+ ],
+ "metadata": {}
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "name": "python3",
+ "language": "python",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.7.3",
+ "mimetype": "text/x-python",
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python",
+ "file_extension": ".py"
+ },
+ "kernel_info": {
+ "name": "python3"
+ },
+ "nteract": {
+ "version": "0.14.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+} \ No newline at end of file
diff --git a/plugins/render_math/test_data/article.nbdata b/plugins/render_math/test_data/article.nbdata
new file mode 100755
index 0000000..bf13538
--- /dev/null
+++ b/plugins/render_math/test_data/article.nbdata
@@ -0,0 +1,5 @@
+Title: An article from a Jupyter notebook
+Date: 2019-03-05 12:14
+Category: Mathematics
+Tags: Linear Algebra, Python, Numpy, Scipy
+Summary: This is a advance part of Linear Algebra with Python \ No newline at end of file
diff --git a/plugins/render_math/test_data/article_with_math_formulas.rst b/plugins/render_math/test_data/article_with_math_formulas.rst
new file mode 100755
index 0000000..87dcc45
--- /dev/null
+++ b/plugins/render_math/test_data/article_with_math_formulas.rst
@@ -0,0 +1,20 @@
+Math formulas
+#############
+
+:date: 2019-09-10
+:yeah: oh yeah !
+:summary: :math:`A_\text{c} = (\pi/4) d^2`
+
+The area of a circle is :math:`A_\text{c} = (\pi/4) d^2`.
+
+.. math::
+
+ α_t(i) = P(O_1, O_2, … O_t, q_t = S_i λ)
+
+ A =
+ \begin{bmatrix}
+ a_{11} & a_{12} & a_{13} \
+ a_{21} & a_{22} & a_{23} \
+ a_{31} & a_{32} & a_{33}
+ \end{bmatrix}
+ \ No newline at end of file
diff --git a/plugins/render_math/test_render_math.py b/plugins/render_math/test_render_math.py
new file mode 100755
index 0000000..b71f4e7
--- /dev/null
+++ b/plugins/render_math/test_render_math.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+from os.path import dirname, join
+from tempfile import TemporaryDirectory
+
+from pelican import Pelican
+from pelican.generators import ArticlesGenerator
+from pelican.settings import configure_settings
+from pelican.tests.support import get_settings, unittest
+from pelican.writers import Writer
+
+from .math import pelican_init, process_rst_and_summaries
+
+
+CUR_DIR = dirname(__file__)
+
+
+class RenderMathTest(unittest.TestCase):
+ def test_ok_on_shared_test_data(self):
+ settings = get_settings(filenames={})
+ settings['PATH'] = join(CUR_DIR, '..', 'test_data')
+ pelican_init(PelicanMock(settings))
+ with TemporaryDirectory() as tmpdirname:
+ generator = _build_article_generator(settings, tmpdirname)
+ process_rst_and_summaries([generator])
+ def test_ok_on_custom_data(self):
+ settings = get_settings(filenames={})
+ settings['PATH'] = join(CUR_DIR, 'test_data')
+ settings['PLUGINS'] = ['pelican-ipynb.markup'] # to also parse .ipynb files
+ configure_settings(settings)
+ pelican_mock = PelicanMock(settings)
+ pelican_init(pelican_mock)
+ Pelican.init_plugins(pelican_mock)
+ with TemporaryDirectory() as tmpdirname:
+ generator = _build_article_generator(settings, tmpdirname)
+ process_rst_and_summaries([generator])
+ for article in generator.articles:
+ if article.source_path.endswith('.rst'):
+ self.assertIn('mathjaxscript_pelican', article.content)
+ generator.generate_output(Writer(tmpdirname, settings=settings))
+
+
+def _build_article_generator(settings, output_path):
+ context = settings.copy()
+ context['generated_content'] = dict()
+ context['static_links'] = set()
+ article_generator = ArticlesGenerator(
+ context=context, settings=settings,
+ path=settings['PATH'], theme=settings['THEME'], output_path=output_path)
+ article_generator.generate_context()
+ return article_generator
+
+class PelicanMock:
+ 'A dummy class exposing the only attributes needed'
+ def __init__(self, settings):
+ self.plugins = []
+ self.settings = settings