math.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # -*- coding: utf-8 -*-
  2. """
  3. Math Render Plugin for Pelican
  4. ==============================
  5. This plugin allows your site to render Math. It uses
  6. the MathJax JavaScript engine.
  7. For markdown, the plugin works by creating a Markdown
  8. extension which is used during the markdown compilation stage.
  9. Math therefore gets treated like a "first class citizen" in Pelican
  10. For reStructuredText, the plugin instructs the rst engine
  11. to output Mathjax for for math.
  12. The mathjax script is automatically inserted into the HTML.
  13. Typogrify Compatibility
  14. -----------------------
  15. This plugin now plays nicely with Typogrify, but it requires
  16. Typogrify version 2.04 or above.
  17. User Settings
  18. -------------
  19. Users are also able to pass a dictionary of settings in the settings file which
  20. will control how the MathJax library renders things. This could be very useful
  21. for template builders that want to adjust the look and feel of the math.
  22. See README for more details.
  23. """
  24. import os
  25. import sys
  26. from pelican import signals
  27. try:
  28. from . pelican_mathjax_markdown_extension import PelicanMathJaxExtension
  29. except ImportError as e:
  30. PelicanMathJaxExtension = None
  31. print("\nMarkdown is not installed, so math only works in reStructuredText.\n")
  32. def process_settings(pelicanobj):
  33. """Sets user specified MathJax settings (see README for more details)"""
  34. mathjax_settings = {}
  35. # NOTE TO FUTURE DEVELOPERS: Look at the README and what is happening in
  36. # this function if any additional changes to the mathjax settings need to
  37. # be incorporated. Also, please inline comment what the variables
  38. # will be used for
  39. # Default settings
  40. mathjax_settings['auto_insert'] = True # if set to true, it will insert mathjax script automatically into content without needing to alter the template.
  41. mathjax_settings['align'] = 'center' # controls alignment of of displayed equations (values can be: left, right, center)
  42. mathjax_settings['indent'] = '0em' # if above is not set to 'center', then this setting acts as an indent
  43. mathjax_settings['show_menu'] = 'true' # controls whether to attach mathjax contextual menu
  44. mathjax_settings['process_escapes'] = 'true' # controls whether escapes are processed
  45. mathjax_settings['latex_preview'] = 'TeX' # controls what user sees while waiting for LaTex to render
  46. mathjax_settings['color'] = 'inherit' # controls color math is rendered in
  47. 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)
  48. 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)
  49. mathjax_settings['responsive'] = 'false' # Tries to make displayed math responsive
  50. mathjax_settings['responsive_break'] = '768' # The break point at which it math is responsively aligned (in pixels)
  51. mathjax_settings['mathjax_font'] = 'default' # forces mathjax to use the specified font.
  52. # Source for MathJax: Works boths for http and https (see http://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn)
  53. mathjax_settings['source'] = "'//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'"
  54. # Get the user specified settings
  55. try:
  56. settings = pelicanobj.settings['MATH_JAX']
  57. except:
  58. settings = None
  59. # If no settings have been specified, then return the defaults
  60. if not isinstance(settings, dict):
  61. return mathjax_settings
  62. # The following mathjax settings can be set via the settings dictionary
  63. for key, value in ((key, settings[key]) for key in settings):
  64. # Iterate over dictionary in a way that is compatible with both version 2
  65. # and 3 of python
  66. if key == 'align':
  67. try:
  68. typeVal = isinstance(value, basestring)
  69. except NameError:
  70. typeVal = isinstance(value, str)
  71. if not typeVal:
  72. continue
  73. if value == 'left' or value == 'right' or value == 'center':
  74. mathjax_settings[key] = value
  75. else:
  76. mathjax_settings[key] = 'center'
  77. if key == 'indent':
  78. mathjax_settings[key] = value
  79. if key == 'show_menu' and isinstance(value, bool):
  80. mathjax_settings[key] = 'true' if value else 'false'
  81. if key == 'auto_insert' and isinstance(value, bool):
  82. mathjax_settings[key] = value
  83. if key == 'process_escapes' and isinstance(value, bool):
  84. mathjax_settings[key] = 'true' if value else 'false'
  85. if key == 'latex_preview':
  86. try:
  87. typeVal = isinstance(value, basestring)
  88. except NameError:
  89. typeVal = isinstance(value, str)
  90. if not typeVal:
  91. continue
  92. mathjax_settings[key] = value
  93. if key == 'color':
  94. try:
  95. typeVal = isinstance(value, basestring)
  96. except NameError:
  97. typeVal = isinstance(value, str)
  98. if not typeVal:
  99. continue
  100. mathjax_settings[key] = value
  101. if key == 'linebreak_automatic' and isinstance(value, bool):
  102. mathjax_settings[key] = 'true' if value else 'false'
  103. if key == 'responsive' and isinstance(value, bool):
  104. mathjax_settings[key] = 'true' if value else 'false'
  105. if key == 'responsive_break' and isinstance(value, int):
  106. mathjax_settings[key] = str(value)
  107. if key == 'tex_extensions' and isinstance(value, list):
  108. # filter string values, then add '' to them
  109. try:
  110. value = filter(lambda string: isinstance(string, basestring), value)
  111. except NameError:
  112. value = filter(lambda string: isinstance(string, str), value)
  113. value = map(lambda string: "'%s'" % string, value)
  114. mathjax_settings[key] = ',' + ','.join(value)
  115. if key == 'mathjax_font':
  116. try:
  117. typeVal = isinstance(value, basestring)
  118. except NameError:
  119. typeVal = isinstance(value, str)
  120. if not typeVal:
  121. continue
  122. value = value.lower()
  123. if value == 'sanserif':
  124. value = 'SansSerif'
  125. elif value == 'fraktur':
  126. value = 'Fraktur'
  127. elif value == 'typewriter':
  128. value = 'Typewriter'
  129. else:
  130. value = 'default'
  131. mathjax_settings[key] = value
  132. return mathjax_settings
  133. def configure_typogrify(pelicanobj, mathjax_settings):
  134. """Instructs Typogrify to ignore math tags - which allows Typogfrify
  135. to play nicely with math related content"""
  136. # If Typogrify is not being used, then just exit
  137. if not pelicanobj.settings.get('TYPOGRIFY', False):
  138. return
  139. try:
  140. import typogrify
  141. from distutils.version import LooseVersion
  142. if LooseVersion(typogrify.__version__) < LooseVersion('2.0.7'):
  143. raise TypeError('Incorrect version of Typogrify')
  144. from typogrify.filters import typogrify
  145. # At this point, we are happy to use Typogrify, meaning
  146. # it is installed and it is a recent enough version
  147. # that can be used to ignore all math
  148. # Instantiate markdown extension and append it to the current extensions
  149. pelicanobj.settings['TYPOGRIFY_IGNORE_TAGS'].extend(['.math', 'script']) # ignore math class and script
  150. except (ImportError, TypeError, KeyError) as e:
  151. pelicanobj.settings['TYPOGRIFY'] = False # disable Typogrify
  152. if isinstance(e, ImportError):
  153. print("\nTypogrify is not installed, so it is being ignored.\nIf you want to use it, please install via: pip install typogrify\n")
  154. if isinstance(e, TypeError):
  155. 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")
  156. if isinstance(e, KeyError):
  157. print("\nA more recent version of Pelican is needed for Typogrify to work with render_math.\nPlease upgrade Pelican to the latest version or clone it directly from the master GitHub branch\nTypogrify will be turned off due to this reason\n")
  158. def process_mathjax_script(mathjax_settings):
  159. """Load the mathjax script template from file, and render with the settings"""
  160. # Read the mathjax javascript template from file
  161. with open (os.path.dirname(os.path.realpath(__file__))+'/mathjax_script_template', 'r') as mathjax_script_template:
  162. mathjax_template = mathjax_script_template.read()
  163. return mathjax_template.format(**mathjax_settings)
  164. def mathjax_for_markdown(pelicanobj, mathjax_settings):
  165. """Instantiates a customized markdown extension for handling mathjax
  166. related content"""
  167. # Create the configuration for the markdown template
  168. config = {}
  169. config['mathjax_script'] = process_mathjax_script(mathjax_settings)
  170. config['math_tag_class'] = 'math'
  171. config['auto_insert'] = mathjax_settings['auto_insert']
  172. # Instantiate markdown extension and append it to the current extensions
  173. try:
  174. pelicanobj.settings['MD_EXTENSIONS'].append(PelicanMathJaxExtension(config))
  175. except:
  176. sys.excepthook(*sys.exc_info())
  177. sys.stderr.write("\nError - the pelican mathjax markdown extension failed to configure. MathJax is non-functional.\n")
  178. sys.stderr.flush()
  179. def mathjax_for_rst(pelicanobj, mathjax_settings):
  180. pelicanobj.settings['DOCUTILS_SETTINGS'] = {'math_output': 'MathJax'}
  181. rst_add_mathjax.mathjax_script = process_mathjax_script(mathjax_settings)
  182. def pelican_init(pelicanobj):
  183. """Loads the mathjax script according to the settings. Instantiate the Python
  184. markdown extension, passing in the mathjax script as config parameter
  185. """
  186. # Process settings
  187. mathjax_settings = process_settings(pelicanobj)
  188. # Configure Typogrify
  189. configure_typogrify(pelicanobj, mathjax_settings)
  190. # Configure Mathjax For Markdown
  191. if PelicanMathJaxExtension:
  192. mathjax_for_markdown(pelicanobj, mathjax_settings)
  193. # Configure Mathjax For RST
  194. mathjax_for_rst(pelicanobj, mathjax_settings)
  195. def rst_add_mathjax(instance):
  196. _, ext = os.path.splitext(os.path.basename(instance.source_path))
  197. if ext != '.rst':
  198. return
  199. # If math class is present in text, add the javascript
  200. if 'class="math"' in instance._content:
  201. instance._content += "<script type='text/javascript'>%s</script>" % rst_add_mathjax.mathjax_script
  202. def register():
  203. """Plugin registration"""
  204. signals.initialized.connect(pelican_init)
  205. signals.content_object_init.connect(rst_add_mathjax)