aboutsummaryrefslogtreecommitdiff
path: root/plugins/render_math/math.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/render_math/math.py')
-rwxr-xr-xplugins/render_math/math.py367
1 files changed, 367 insertions, 0 deletions
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)