diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/gladtex |
Initial commit
Diffstat (limited to 'support/gladtex')
26 files changed, 6761 insertions, 0 deletions
diff --git a/support/gladtex/COPYING b/support/gladtex/COPYING new file mode 100644 index 0000000000..65c5ca88a6 --- /dev/null +++ b/support/gladtex/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/support/gladtex/ChangeLog b/support/gladtex/ChangeLog new file mode 100644 index 0000000000..2c1f932c96 --- /dev/null +++ b/support/gladtex/ChangeLog @@ -0,0 +1,184 @@ +3.0.0 + + - new features and incompatible changes: + - add `-P` command-line switch to be used as a Pandoc document filter, + see <https://pandoc.org/filters.html> + - add environment variable `GLADTEX_ARGS` to pass command-line + switches when used as pandocfilter where passing additional + arguments is impossible + - redefine colour handling: use xcolor package, therefore handling + text and background colour the same way for both PNG and SVG + - add SVG support for scalable images + - use SVG output by default + - gleetex.htmlhandling.HtmlImageFormatter: rename link_path to + link_prefix + - bug fixes: + - correctly parse HTML5 file encoding declarations + - add more exceptions to the unicode table for the unicode replacement + mode (see -R) + - treat -d as a relative path + +2.3.1 Avoid useless spaces + + - When formula replacement with `-R` is requested, it could happen that + additional spaces were inserted, even if not necessary. "für" would for + instance become "f\"{u} r". Fixed. + +2.3 Fix formula sizing + + - It seems as if 16px / 12pt were the default font size these days for + browsers. Therefore, the default resolution has been set to 115 DPI. + Furthermore, the DPI switch now accepts pt values for fontsizes and will + calculate the corresponding DPI itself. + - When the environment variable `DEBUG=1` is set, the full backtrace will be + printed. + - Extend unicode table creation script to allow blacklisting of certain + commands. + +2.2.1 - fix handling of non-ascii alphabetical characters + + - replace characters with diacritics in the LaTeX source, but keep the + unmodified character in image alt attribute (for better readibility) + +2.2 - make alternative text of formulas more readable + + - replace formatting commands in alt attribute; this shortens the + formula and makes it mor readable + - replace unicode signs also in alt attribute (good for screen reader + users and text-mode browsers) + - recognize upper-case `ENV` attribute of `EQ` tag (so that e.g. + displaymath is recognized correctly) + +2.1.1 Bug Fix Release + + - treat eq element content as verbatim + - decode HTML entities within formula tags + +2.1 add support for unicode math with translation table + - handle subprocess stdin and stdout encoding properly + - set UTF-8 as encoding for all LaTeX documents + - add -R option (replace non ascii characters) + - formulas in .htex documents may now contain umlauts or unicode math + characters; conversion will work without adjustments, only -R has to + be specified + - handle encoding better and more strictly for LaTeX 2E + +2.0.1 Bug Fix Release + - show user a meaningful error message if LaTeX or dvipng is missing + - setup.py: build manual page, if pandoc present + - freeze multiprocessing on Windows, to make executables distributable + +2.0 - make GladTeX truely platform independent + - add formula number in error output; makes tracking of formulas easier in + error case + - write man page + - set css class correctly for display math formulas + - HTML label/id generation: + - do not create overlong id's + - do only generate id's starting with an alphabetical character + - squeeze multiple identical characters + - reparse outsourced formulas correctly (was a mixture of formatted vs. + unformatted formulas) + - do not use absolute links when operating on file which is not in current + working directory + - be more careful with backslashes vs. slashes + - allow formulas consisting only of numbers (i.e. example calculations) by + prefixing "form_" in front of the HTML id (id must start with a letter + but may be followed by digits) + - allow removal of unreadable caches with the `-n` switch (extend library + with this functionality) + - introduce `-m` switch to print the output in a less concise, but more + machine-parseable format + +1.6 - complete rewrite + - rewrite GladTeX fully in Python + - allows easy compilation into a binary for a specific platform + - comes with a new library to use GladTeX functionality within other + applications + - fully unit-tested + - enable piping support; GladTeX can read from stdin and write to stdout + - drop -t switch; image is either transparent by default or has a background + color which can be set with -b + - drop -s switch + - introduce -o (output) option + - introduce new cache format containing version numbers; json, so + interface to other programming languages + +1.5 - Introduce options to make embedding of GladTeX easier + - Try to parse LaTeX's error output and display it to help users to find the + issue quicker and to make GladTeX better embeddable. + - Add option to remove error log file, produced by LaTeX, automatically. + - Rewrite some help messages. + - Add signal handling to get meaningful error messages if GladTeX hangs. + +1.4.2 - bug fix release + - Add some eval's to cope with some failures. + - Since there were some incompatibilities between Perl 5.10 and 5.18 in how + the cache of the generated images is stored, GladTeX now removes this file + along with all images starting with "eqn" and generates them again. + +1.4.1 - bug fix release + - Remove desc.html if created and empty + +1.4 - put LaTeX equations into alt tag for text-mode browsers and blind people + (and disabled images) + - If requested (-a), exclude equations longer than 80 characters in an extra + file and make the equation image a link to the longer, excluded image + alternative + - eqn2img: patch to allow building on windows + - Change build system from make to cmake + - Refactored gladtex code a lot to allow the usage of "use strict/use + warnings" + - Fix bug where multiple equations couldn't be on a single (html) line + - Rework manpage + +1.3 - Un-escape common entities before processing equations + - Update man page with CSS class options + - Add support for setting the CSS class of images when the + environment is "math" or "displaymath" + - eqn2img: changed redirection syntax (from dvips to /dev/null) + for portability + - GladTeX: exit with status 1 when a closing EQ tag is missing + - GladTeX: print error messages to stderr instead of stdout + - Fix environment-passing to eqn2img + - Add support for a "dpi" attribute on EQ tags to customize the + DPI used for each equation + +1.2 - Fixed a serious memory allocation error, pointed out by Eric J. + Francois. Also fixed several leaks. + - Added full alpha channel to PNG files (also suggested by Eric) + - The -e option was ignored, fixed (pointed out by Andr\'e Schleife) + - Added man page, contributed by Volker Schatz + +1.1 - Portability fixes: Do not assume a specific location for perl + (use "env" in the shebang line) and do not rely on the bash style + "&>" redirection. + +1.0 - Image alignment workaround (most browsers interpret + "ALIGN=MIDDLE" somewhat strangely, so it has been changed + to "STYLE=vertical-align: -xx") + - Added cache file, so that gladtex doesn't have to regenerate + images for equations that haven't changed. + - Added ENV option (as in <EQ ENV=align*>) to support environments + other than "displaymath". + - Bug fixes. + +0.3 - Added BoundingBox workaround (dvips sometimes outputs wrong + BoundingBox, for instance when using \mathbb{}) + - Moved the whole "LaTeX eqn to image" conversion into the C code, + turning the C program (renamed from pngmodify to eqn2img) into + a standalone utility (e.g. echo '\sqrt{2}' | eqn2img -o + eqn.png). + - Added colour options (-c -b and -t). + - Fixed bug causing segfault when adding space _above_ an image. + - Fixed image reusing bug (in 0.2 image reuse didn't work across + separate files when processing files outside startup cwd). + - And some other minor bugs and cosmetic changes. + - Makefile added to distribution + +0.2 - First official release, completely rewritten code. + +0.1 - Used only internally at the Dept. of Mathematics at the Univ. of Oslo + July-August 1999 (for the project "Matteknekker'n") under the name + htmleqn. +# vim: set expandtab sts=4 ts=4 sw=4 expandtab: diff --git a/support/gladtex/README.md b/support/gladtex/README.md new file mode 100644 index 0000000000..9fa684e08e --- /dev/null +++ b/support/gladtex/README.md @@ -0,0 +1,125 @@ +GladTeX +======= + +GladTeX is a preprocessor that enables the use of LaTeX formulas within HTML +files. The formulas, embedded in <eq>...</eq> tags, as if within $$..$$ in LaTeX, +is fed through latex and replaced by images. + +Additionally all images get an alt-tag for alternative texts that contains the +LaTeX-equivalent of the image. This is handy for text-mode browsers or blind +screen reader users. + +This is a complete rewrite of the old GladTeX which was implemented in Perl and +in C. The old version was not easily portable across platforms. The +new version is purely implemented in Python, gets rid of the Ghostscript +dependency and additionally offers the GladTeX functionality in a Python module +called gleetex. to be embedded in other applications. + +Features +-------- + +- both program and library: GladTeX and GleeTeX (both Python) +- easy-to-adjust font size of formulas through command-line switch +- customisation of commands using a custom LaTeX preamble +- automated replacement of non-ascii characters through LaTeX control + sequences +- usage as Pandoc filter for export in HTML-based formats as for instance Epub + +License +------- + +- (C) 1999-2010 Martin G. Gulbrandsen +- (C) 2011-2013 Jonathan Daugherty (especially release 1.3) +- (C) 2013-2018 Sebastian Humenda + +This program is distributed under the LGPL-3, or at your option, any later +version of the license; for details see the accompanying file COPYING. + +The official project homepage is at <http://humenda.github.io/GladTeX> + +Installation +============ + +### Debian/Ubuntu + +On all derivatives of Debian (as Ubuntu/Mint, etc.), installing GladTeX is as +easy as + + # apt-get install gladtex + +### Windows + +If you want to use the program without the Python library, you should download a +pre-compiled binary from <https://github.com/humenda/GladTeX/releases>. + +Just unzip the archive and move the files to a directory within `%PATH%`. + +### From Source + +The following is required for installing GladTeX: + +- Python >= 3.4 +- LaTeX (2e), dvipng +- the LaTeX package preview.sty + + +#### Debian / Ubuntu + +On Debian/Ubuntu systems the following commands will satisfy the dependencies: + + # apt-get install python3-all texlive-fonts-recommended texlive-latex-recommended preview-latex-style dvipng + +The package can then be installed using + + # python3 setup.py install + +Note: If your system ships `python` as the command for Python3 you have to use +`python in` the above command instead. + +#### OS X + +You need to install a LaTeX distribution on your Mac. GladTeX was successfully +run with [MacTex](http://www.tug.org/mactex/). + +You can download a zip source archive from +[GitHub](https://github.com/humenda/GladTeX) or use git: + + $ git clone https://github.com/humenda/GladTeX.git + +Use `cd` to change to the GladTeX source directory and issue + + $ python setup.py install + + + +### Compilation On Windows + +To compile GladTeX on Windows, yu need a Python3 installation. Assumed is +python3.4, newer versions should work fine as well. Only the paths need to be +adjusted + +Install py2exe: + + c:\python34\scripts\pip.exe install py2exe + +Given that GladTeX is located in c:\users\user\gladtex: + + cd c:\users\user\gladtex + c:\python34\python.exe setup.py install + +That will install GladTeX as a library to `c:\python34\lib` and the script to +`c:\python34\scripts`. It also allows py2exe to find the gleetex module. Now the +executable can be build: + + c:\python34\scripts\build_exe.exe -b 0 -c gladtex.py + +That'll create a `dist/` folder containing the executable. If you have other +python applications in your project it is useful to read about the `-b` switch +to share some python components included in the just-built executable. + +Documentation +------------- + +Please use `man gladtex` for further instructions or have a look at the file +[manpage.md](manpage.md). + diff --git a/support/gladtex/ToDo.FAQ b/support/gladtex/ToDo.FAQ new file mode 100644 index 0000000000..923c9836ba --- /dev/null +++ b/support/gladtex/ToDo.FAQ @@ -0,0 +1,6 @@ +A few items which could go into a FAQ: + +What do the error messages mean? +Why is my formula positioned awkardly within the text? (most probably displaymath instead of inline math) +Why are limits over sums and similar not correctly set? (use displaymath, sometimes `\\limits`) +Why don't my special characters like (unicode) math symbols or umlauts not work diff --git a/support/gladtex/ToDo.md b/support/gladtex/ToDo.md new file mode 100644 index 0000000000..e5a00c6ea7 --- /dev/null +++ b/support/gladtex/ToDo.md @@ -0,0 +1,28 @@ +To Do +===== + +This list contains things to be implemented in GladTeX. If you have additions or +even feel like you want to do it, feel free to drop me an email: `shumenda |aT| +gmx //dot-- de`. + +Uncategorized +------------- + +- introduce command line option which will check whether all all formulas in a + cache are used and if not, remove the formula (only useful for caches + corresponding to a single document) + +Gettext +------- + + +Gettext should be integrated to localize messages (especially errors). + +Compressed Cache +---------------- + +The cache stores the path, the formula and the positioning of an image. For +large documents, this might be quite big, hence it makes sense to compress them. + +To make things easier, the cache should have a .gz extension. + diff --git a/support/gladtex/create_windows_distributions.py b/support/gladtex/create_windows_distributions.py new file mode 100644 index 0000000000..3c7ca2c6cf --- /dev/null +++ b/support/gladtex/create_windows_distributions.py @@ -0,0 +1,140 @@ +""" +This file builds windows distributions, zip files with GladTeX and all other +files.""" +import os +import shutil +import stat +import sys +import zipfile +import gleetex + +def exec_setup_py(arg_string): + """Execute `python setup.py` as a subprocess. Use Wine, if necessary.""" + ret = None + if sys.platform.startswith('win'): + ret = os.system('python setup.py ' + arg_string) + else: + if not shutil.which('wine'): + print("Error: Wine is not installed, aborting…") + sys.exit(5) + ret = os.system('wine python setup.py ' + arg_string) + if ret: + if sys.platform.startswith('win'): + print("Aborting at command `python setup.py %s`." % arg_string) + else: + print("Aborting at command `wine python setup.py %s`." % arg_string) + sys.exit(7) + +def get_python_version(): + """Return the python version as a string.""" + import re, subprocess + args = ['python', '--version'] + if not sys.platform.startswith('win'): + args = ['wine'] + args + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + stdout = proc.communicate()[0].decode(sys.getdefaultencoding()) + if proc.wait(): + raise TypeError("Abnormal subprocess termination while querying python version.") + return re.search(r'.*?(\d+\.\d+\.\d+)', stdout).groups()[0] + +def get_executable_name(label): + """Construct the name of an executable""" + return 'gladtex-win64-%s-py_%s-%s.zip' % (gleetex.VERSION, get_python_version(), + label) + +def bundle_files(src, output_name): + """Bundle the compiled binary files with README, ChangeLog and COPYING.""" + if os.path.exists(output_name): + shutil.rmtree(output_name) + os.rename(src, output_name) + # add README.first + with open(os.path.join(output_name, 'README.first.txt'), 'w') as f: + f.write('GladTeX for Windows\r\n===================\r\n\r\n') + f.write('This program has been compiled with python 3.4.4. If you want to embedd it in binary form with your binary python application, the version numbers HAVE TO match.\r\n') + f.write('\r\nFor more information, see the file README.md or http://humenda.github.io/GladTeX\r\n') + + # copy README and other files + for file in ['README.md', 'COPYING', 'ChangeLog']: + dest = os.path.join(output_name, file) + # check whether file ending exists + if not '.' in dest[-5:]: + dest += '.txt' + shutil.copy(file, dest) + + files = [os.path.join(root, file) + for root, _, files in os.walk(output_name) for file in files] + with zipfile.ZipFile(output_name + '.zip', 'w', zipfile.ZIP_DEFLATED) as z: + for file in files: + z.write(file) + shutil.rmtree(output_name) + +class TemporaryBuildDirectory(): + """Context handler to guard the build process. + Upon entering the context, the source is copied to a temporary directory and + the program changes to this directory. After all build actions have been + done, the output file is copied back to the original directory, the program + resets the current working directory and deletes the temporary directory.""" + def __init__(self, output_file_name): + self.orig_cwd = os.getcwd() + self.tmpdir = None + self.output_file_name = output_file_name + + def __enter__(self): + self.tmpdir = self.get_temp_directory() + shutil.copytree(os.getcwd(), self.tmpdir) + os.chdir(self.tmpdir) + return self + + def __exit__(self, _a, _b, _c): + os.chdir(self.orig_cwd) + shutil.copy(os.path.join(self.tmpdir, self.output_file_name), + self.output_file_name) + shutil.rmtree(self.tmpdir, onerror=self.__onerror) + + def get_temp_directory(self): + """Find a temporary directory to work in. The checks are done to find a + directory which does not reside within the user's path, because py2exe + includes absolute paths for python scripts (in their tracebacks). It is not + desirable to show the whole world the directory layout of the computer where + the source code was built on.""" + tmp_base = None + if os.path.exists('/tmp'): + tmp_base = '/tmp' + elif os.path.exists('\\temp'): + tmp_base = '\\temp' + elif os.path.exists('\\windows\\temp'): + tmp_base = '\\windows\\temp' + else: + import tempfile + tmp_base = tempfile.gettempdir() + tmpdir = os.path.join(tmp_base, 'gladtex.build') + if os.path.exists(tmpdir): + shutil.rmtree(tmpdir, onerror=self.__onerror) + return tmpdir + + def __onerror(self, func, path, exc_info): + """ + Error handler for ``shutil.rmtree``. + If the error is due to an access error (read only file) it attempts to + add write permission and then retries. If the error is for another reason it re-raises the error. + Usage : ``shutil.rmtree(path, onerror=onerror)``.""" + if not os.access(path, os.W_OK): + # Is the error an access error ? + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise exc_info + + +if __name__ == '__main__': + with TemporaryBuildDirectory(get_executable_name('embeddable')) as tb: + # build embeddable release, where all files are separate DLL's; if somebody + # distributes a python app, these DLL files can be shared + exec_setup_py('py2exe -c -O 2 -i gleetex --bundle-files 3') + bundle_files('dist', os.path.splitext(tb.output_file_name)[0]) + + # create a stand-alone version of GladTeX + with TemporaryBuildDirectory(get_executable_name('standalone')) as tb: + exec_setup_py('py2exe -i gleetex -c -O 2 --bundle-files 1') + bundle_files('dist', os.path.splitext(tb.output_file_name)[0]) + diff --git a/support/gladtex/examples/md2epub.py b/support/gladtex/examples/md2epub.py new file mode 100644 index 0000000000..0354cfeb70 --- /dev/null +++ b/support/gladtex/examples/md2epub.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""This demo script converts from markdown to Epub using GladTeX. It requires +Pandoc for the conversion. + +Throughout this script, the abbreviation AST for Abstract Syntax Tree is used.""" + +import json +import os +import shutil +import subprocess +import sys + +import gleetex + +def transform_ast(ast): + # extract formulas from Pandoc document AST + formulas = gleetex.pandoc.extract_formulas(ast) + # converter using cache, helps avoiding the same formula twice + conv = gleetex.cachedconverter.CachedConverter('.', True, + encoding="UTF-8") + # automatically handle unicode + conv.set_replace_nonascii(True) + # go parallel + conv.convert_all('.', formulas) + + # an converted image has information like image depth and height, adjust + # data structure for write-back + formulas = [conv.get_data_for(eqn, style) for _p, style, eqn in formulas] + # get a formatter instance + with gleetex.htmlhandling.HtmlImageFormatter(".") as img_fmt: + # non-ascii sequences will be replaced in the laternative text + img_fmt.set_replace_nonascii(True) + # this alters the AST reference, so no return value required + gleetex.pandoc.replace_formulas_in_ast(img_fmt, ast['blocks'], + formulas) + +def cleanup(path): + # remove images and ache, relevant data is included within the EPUB + for file in os.listdir(path): + if file.endswith('.png') or file.endswith('.cache'): + os.remove(os.path.join(path, file)) + +def main(): + for prog in ('pandoc', 'gladtex'): + if not shutil.which(prog): + sys.stderr.write(('This script requires %s, please install and rerun ' + 'this script.') % prog) + sys.exit(1) + + usage = False + if len(sys.argv) < 2: + print("Missing command arguments.") + usage = True + elif len(sys.argv) > 2 or (len(sys.argv) == 2 and not + os.path.exists(sys.argv[1])): + print("Exactly one input path required") + usage = True + if usage: + print("%s <INPUTFILE>\n\nConvert given file to epub using GladTeX." % \ + sys.argv[0]) + sys.exit(0) + + inputfile = sys.argv[1] + outputfile = '%s.epub' % os.path.splitext(inputfile)[0] + # get the document AST + proc = subprocess.Popen(['pandoc', '-t', 'json', inputfile], + stdout=subprocess.PIPE) + ast = json.loads(proc.communicate()[0].decode(sys.getdefaultencoding())) + if proc.wait() != 0: + sys.exit(2) + + # the actual GleeTeX calls are here + transform_ast(ast) + + # write back to stdin of pandoc + proc = subprocess.Popen(['pandoc', '-o', outputfile, '-f', 'json', '-t', + 'epub'], stdin=subprocess.PIPE) + proc.communicate(json.dumps(ast).encode(sys.getdefaultencoding())) + if proc.wait(): + sys.exit(2) + cleanup('.') + +if __name__ == '__main__': + main() diff --git a/support/gladtex/gladtex.py b/support/gladtex/gladtex.py new file mode 100644 index 0000000000..c9c991dc93 --- /dev/null +++ b/support/gladtex/gladtex.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +import argparse +import multiprocessing +import os +import posixpath +import sys +import gleetex +from gleetex import parser + + +class HelpfulCmdParser(argparse.ArgumentParser): + """This variant of arg parser always prints the full help whenever an error + occurs.""" + def error(self, message): + sys.stderr.write('error: %s\n' % message) + self.print_help() + sys.exit(2) + + + +def format_ordinal(number): + endings = ['th', 'st', 'nd', 'rd'] + ['th'] * 6 + return '%d%s' % (number, endings[number%10]) + +class Main: + """This class parses command line arguments and deals with the + conversion. Only the run method needs to be called.""" + def __init__(self): + self.__encoding = "utf-8" + + def _parse_args(self, args): + """Parse command line arguments and return option instance.""" + epilog = "GladTeX %s, http://humenda.github.io/GladTeX" % gleetex.VERSION + description = ("GladTeX is a preprocessor that enables the use of LaTeX" + " maths within HTML files. The maths, embedded in <EQ>...</EQ> " + "tags, as if within \\(..\\) in LaTeX (or $...$ in TeX), is fed " + "through latex and replaced by images.\n\nPlease also see the " + "documentation on the web or from the manual page for more " + "information, especially on environment variables.") + cmd = HelpfulCmdParser(epilog=epilog, description=description) + cmd.add_argument("-a", action="store_true", dest="exclusionfile", help="save text alternatives " + + "for images which are too long for the alt attribute into a " + + "single separate file and link images to it") + cmd.add_argument('-b', dest='background_color', + help=("Set background color for resulting images " + "(default transparent, use hex)")) + cmd.add_argument('-c', dest='foreground_color', + help=("Set foreground color for resulting images (default " + "000000, hex)")) + cmd.add_argument('-d', dest='directory', help="Directory in which to" + + " store generated images in (relative to the output file)") + cmd.add_argument('-e', dest='latex_maths_env', + help="Set custom maths environment to surround the formula" + \ + " (e.g. flalign)") + cmd.add_argument('-f', metavar='SIZE', dest='fontsize', default=12, + help="Set font size in pt (default 12)") + cmd.add_argument('-E', dest='encoding', default=None, + help="Overwrite encoding to use (default UTF-8)") + cmd.add_argument('-i', metavar='CLASS', dest='inlinemath', + help="CSS class to assign to inline math (default: 'inlinemath')") + cmd.add_argument('-l', metavar='CLASS', dest='displaymath', + help="CSS class to assign to block-level math (default: 'displaymath')") + cmd.add_argument('-K', dest='keep_latex_source', action="store_true", + default=False, help="keep LaTeX file(s) when converting formulas (useful for debugging)") + cmd.add_argument('-m', dest='machinereadable', action="store_true", + default=False, + help="Print output in machine-readable format (less concise, better parseable)") + cmd.add_argument("-n", action="store_true", dest="notkeepoldcache", + help=("Purge unreadable caches along with all eqn*.png files. " + "Caches can be unreadable if the used GladTeX version is " + "incompatible. If this option is unset, GladTeX will " + "simply fail when the cache is unreadable.")) + cmd.add_argument('-o', metavar='FILENAME', dest='output', + help=("Set output file name; '-' will print text to stdout (by" + "default input file name is used and .htex extension changed " + "to .html)")) + cmd.add_argument('-p', metavar='LATEX_STATEMENT', dest="preamble", + help="Add given LaTeX code to preamble of document; that'll " +\ + "affect the conversion of every image") + cmd.add_argument('-P', dest="pandocfilter", action='store_true', + help="Use GladTeX as a Pandoc filter: read a Pandoc JSON AST " + "from stdin, convert the images, change math blocks to " + "images and write JSON to stdout") + cmd.add_argument('--png', action='store_true', dest='png', + help="Use PNG instead of SVG for images") + cmd.add_argument('-r', '--resolution', metavar='DPI', dest='dpi', + default=None, + help=("Set resolution in DPI, only available if PNG output " + "selected; also see `-f`")) + cmd.add_argument('-R', action="store_true", dest='replace_nonascii', + default=False, help="Replace non-ascii characters in formulas " + "through their LaTeX commands") + cmd.add_argument("-u", metavar="URL", dest='url', + help="URL to image files (relative links are default)") + cmd.add_argument('input', help="Input .htex file with LaTeX " + + "formulas (if omitted or -, stdin will be read)") + return cmd.parse_args(args) + + def exit(self, text, status): + """Exit function. Could be used to register any clean up action.""" + sys.stderr.write(text) + if not text.endswith('\n'): + sys.stderr.write('\n') + sys.exit(status) + + def validate_options(self, opts): + """Validate certain arguments suppliedon the command line. The user will + get a (hopefully) helpful error message if he/she gave an invalid + parameter.""" + if opts.fontsize and opts.dpi: + print("Options -f and -d can't be used at the same time.") + sys.exit(14) + if opts.dpi and not opts.png: + print(("Impossible to set resolution when using SVG as output, " + "try -f")) + sys.exit(14) + + def get_input_output(self, options): + """Determine whether GladTeX is reading from stdin/file, writing to + stdout/file and determine base_directory if files are in another + directory. + If no output file name is given and there is a input file to read + from, output is written to a file ending on .html instead of .htex. + The returned document is either string or byte, the latter if encoding + is unknown.""" + data = None + output = '-' + if options.input == '-': + data = sys.stdin.read() + else: + try: + # if encoding was specified or if a pandoc filter is supplied, + # read document with default encoding + if options.encoding or options.pandocfilter: + encoding = ('UTF-8' if options.pandoc else options.encoding) + with open(options.input, encoding=encoding) as f: + data = f.read() + else: # read as binary and guess from HTML meta charset + with open(options.input, 'rb') as file: + data = file.read() + except UnicodeDecodeError as e: + self.exit(('Error while reading from %s: %s\nProbably this file' + ' has a different encoding, try specifying -E.') % \ + (options.input, str(e)), 88) + except IsADirectoryError: + self.exit("Error: cannot open %s for reading: is a directory." \ + % options.input, 19) + except FileNotFoundError: + self.exit("Error: file %s not found." % options.input, 20) + + # check which output file name to use + if options.output: + output = options.output + elif options.input != '-': + output = os.path.splitext(options.input)[0] + '.html' + + # else case: output = '-' (see above) + base_path = '' + if options.output and os.path.dirname(options.output): + base_path = os.path.dirname(output) + elif options.input != '-' and os.path.dirname(options.input): + base_path = os.path.dirname(options.input) + if base_path: # if finally a basepath found:, strip \\ if on Windows + base_path = posixpath.join(*(base_path.split('\\'))) + # the basepath needs to be relative to the output file + return (data, base_path, output) + + + def run(self, args): + options = self._parse_args(args[1:]) + self.validate_options(options) + self.__encoding = options.encoding + fmt = ('pandocfilter' if options.pandocfilter else 'html') + doc, base_path, output = self.get_input_output(options) + old_cwd = os.getcwd() + if base_path: + os.chdir(base_path) + try: + # doc is either a list of raw HTML chunks and formulas or a tuple of + # (document AST, list of formulas) if options.pandocfilter + self.__encoding, doc = parser.parse_document(doc, fmt) + except gleetex.parser.ParseException as e: + input_fn = ('stdin' if options.input == '-' else options.input) + self.exit('Error while parsing {}: {}'.format(input_fn, + str(e)), 5) + + link_path = (options.directory if options.directory else '') + processed = self.convert_images(doc, link_path, options) + with gleetex.htmlhandling.HtmlImageFormatter(base_path=link_path, + link_path=options.url) as img_fmt: + img_fmt.set_exclude_long_formulas(True) + if options.replace_nonascii: + img_fmt.set_replace_nonascii(True) + if options.url: + img_fmt.set_url(options.url) + if options.inlinemath: + img_fmt.set_inline_math_css_class(options.inlinemath) + if options.displaymath: + img_fmt.set_display_math_css_class(options.displaymath) + + os.chdir(old_cwd) + with (sys.stdout if output == '-' + else open(output, 'w', encoding=self.__encoding)) as file: + if options.pandocfilter: + gleetex.pandoc.write_pandoc_ast(file, processed, img_fmt) + else: + gleetex.htmlhandling.write_html(file, processed, img_fmt) + + def convert_images(self, parsed_document, base_path, options): + """Convert all formulas to images and store file path and equation in a + list to be processed later on.""" + base_path = ('' if not base_path or base_path == '.' else base_path) + result = [] + try: + conv = gleetex.cachedconverter.CachedConverter(base_path, + not options.notkeepoldcache, encoding=self.__encoding) + except gleetex.caching.JsonParserException as e: + self.exit(e.args[0], 78) + + self.set_options(conv, options) + if options.pandocfilter: + formulas = parsed_document[1] + else: # HTML chunks from EqnParser + formulas = [c for c in parsed_document if isinstance(c, (tuple, + list))] + try: + conv.convert_all(formulas) + except gleetex.cachedconverter.ConversionException as e: + self.emit_latex_error(e, options.machinereadable, + options.replace_nonascii) + + if options.pandocfilter: + # return (ast, formulas), just with formulas being replaced with the + # conversion data + return (parsed_document[0], [conv.get_data_for(eqn, style) + for _p, style, eqn in formulas]) + for chunk in parsed_document: + # output of EqnParser: list-alike is formula, str is raw HTML + if isinstance(chunk, (tuple, list)): + _p, displaymath, formula = chunk + try: + result.append(conv.get_data_for(formula, displaymath)) + except KeyError as e: + raise KeyError(("formula '%s' not found; that means it was " + "not converted which should usually not happen.") % e.args[0]) + else: + result.append(chunk) + return result + + + def set_options(self, conv, options): + """Apply options from command line parser to the converter.""" + # set options + options_to_query = ['preamble', 'latex_maths_env', + 'png', 'keep_latex_source', 'foreground_color', + 'background_color'] + for option_str in options_to_query: + option = getattr(options, option_str) + if option: + if option in ('True', 'False', 'false', 'true'): + option = bool(option) + conv.set_option(option_str, option) + if options.dpi: + conv.set_option("dpi", float(options.dpi)) + elif options.fontsize: + conv.set_option("fontsize", options.fontsize) + if options.replace_nonascii: + conv.set_replace_nonascii(True) + + def emit_latex_error(self, err, machine_readable, escape): + """Format a LaTeX error in a meaningful way. The argument escape + specifies, whether the -R switch had been passed. If the pandocfilter + mode is active, formula positions will be omitted; this makes the code + more complex.""" + if 'DEBUG' in os.environ and os.environ['DEBUG'] == '1': + raise err + escaped = err.formula + if escape: + escaped = gleetex.typesetting.escape_unicode_maths(err.formula) + msg = None + additional = '' + if 'Package inputenc' in err.args[0]: + additional += ('Add the switch `-R` to automatically replace unicode ' + 'characters with LaTeX command sequences.') + if machine_readable: + msg = 'Number: {}\nFormula: {}{}\nMessage: {}'.format(err.formula_count, + err.formula, + ('' if escaped == err.formula + else '\nLaTeXified formula: %s' % escaped), + err.cause) + if err.src_line_number and err.src_pos_on_line: + msg = ('Line: {}, {}\n' + msg).format(err.src_line_number, + err.src_pos_on_line) + if additional: + msg += '; ' + additional + else: + formula = ' ' + err.formula.replace('\n', '\n ') + escaped = (' ' + escaped.replace('\n', '\n ') if escaped != + err.formula else '') + msg = "Error while converting formula %d\n" % err.formula_count + if err.src_line_number and err.src_pos_on_line: + msg += " at line %d, %d:\n" % (err.src_line_number, + err.src_pos_on_line,) + msg += '%s%s\n%s' % (formula, ('' + if not escaped or escaped == err.formula + else '\nFormula without unicode symbols:\n%s' % escaped), + err.cause) + if additional: + import textwrap + msg += ' undefined.\n' + '\n'.join(textwrap.wrap(additional, 80)) + self.exit(msg, 91) + + +def main(): + """Entry point for setuptools.""" + # enable multiprocessing on Windows, see python docs + multiprocessing.freeze_support() + m = Main() + # run as pandoc filter? + args = sys.argv # fallback if no environment variable set + if 'GLADTEX_ARGS' in os.environ: + args = [sys.argv[0]] + os.environ['GLADTEX_ARGS'].split(' ') + if '-P' not in args: + args = [args[0]] + ['-P'] + args[1:] + ['-'] + m.run(args) + +if __name__ == '__main__': + main() diff --git a/support/gladtex/gleetex/__init__.py b/support/gladtex/gleetex/__init__.py new file mode 100644 index 0000000000..89bfbed854 --- /dev/null +++ b/support/gladtex/gleetex/__init__.py @@ -0,0 +1,12 @@ +from . import caching +from . import cachedconverter +from . import htmlhandling +from . import image +from . import pandoc +from . import parser +from . import typesetting + +VERSION = '3.0.0' + +__all__ = ['caching', 'cachedconverter', 'document', 'htmlhandling', 'image', + 'pandoc', 'parser', 'unicode', 'VERSION'] diff --git a/support/gladtex/gleetex/cachedconverter.py b/support/gladtex/gleetex/cachedconverter.py new file mode 100644 index 0000000000..04161aab71 --- /dev/null +++ b/support/gladtex/gleetex/cachedconverter.py @@ -0,0 +1,235 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""In order to convert images only if they are not already cached, the cached +converter sacrifices customizability for convenience and provides a class +converting a formula directly to a png file.""" + +import concurrent.futures +import multiprocessing +import os +import subprocess + +from . import caching, image, typesetting +from .caching import normalize_formula +from .image import Format + +class ConversionException(Exception): + """This exception is raised whenever a problem occurs during conversion. + Example: + c = ConversionException("cause", "\\tau", 10, 38, 5) + assert c.cause == cause + assert c.formula == '\\tau' + assert c.src_line_number == 10 # line number in source document (counting from 1) + assert c.src_pos_on_line == 38 # position of formula in source line, counting from 1 + assert c.formula_count == 5 # fifth formula in document (starting from 1) + """ + # mind your own business mr. pylint: + #pylint: disable=too-many-arguments + def __init__(self, cause, formula, formula_count, src_line_number=None, + src_pos_on_line=None): + # provide a default error message + if src_line_number and src_pos_on_line: + super().__init__("LaTeX failed at formula line {}, {}, no. {}: {}".format( + src_line_number, src_pos_on_line, formula_count, cause)) + else: + super().__init__("LaTeX failed at formula no. {}: {}".format( + formula_count, cause)) + # provide attributes for upper level error handling + self.cause = cause + self.formula = formula + self.src_line_number = src_line_number + self.src_pos_on_line = src_pos_on_line + self.formula_count = formula_count + +class CachedConverter: + """Convert formulas to images. + + c = CachedConverter(base_path) + for formula in [... formulas ...]: + pos, file_path = c.convert(formula) + ... + + The formula is either converted or retrieved from a cache in the same + directory like the images. + + :param base_path directory to place files in (relative to the file + converted. So if the file being converted is foo/bar.htex and the + base_path is set to img, the image will be placed in foo/img/. + :param keep_old_cache If an existing cache cannot be read (incompatible + GladTeX version, ...) Aand the flag is set, the program will simply + crash and tell the user to remove the cache (default). If set to False, + the program will instead remove the cache and all eqn* files and + recreate the cache. + :param encoding The encoding for the LaTeX document, default None + """ + GLADTEX_CACHE_FILE_NAME = 'gladtex.cache' + + def __init__(self, base_path, keep_old_cache=True, encoding=None): + cache_path = os.path.join(base_path, + CachedConverter.GLADTEX_CACHE_FILE_NAME) + self.__base_path = base_path + self.__cache = caching.ImageCache(cache_path, + keep_old_cache=keep_old_cache) + self.__converter = None + self.__options = {'dpi': None, 'transparency': None, 'fontsize': None, + 'background_color' : None, 'foreground_color' : None, + 'preamble' : None, 'latex_maths_env' : None, + 'keep_latex_source': False, 'png': False} + self.__encoding = encoding + self.__replace_nonascii = False + + + def set_option(self, option, value): + """Set one of the options accepted for gleetex.image.Tex2img. It is a + proxy function. + `option` must be one of dpi, fontsize, transparency, background_color, + foreground_color, preamble, latex_maths_env, keep_latex_source, png.""" + if not option in self.__options.keys(): + raise ValueError("Option must be one of " + \ + ', '.join(self.__options.keys())) + self.__options[option] = value + + def set_replace_nonascii(self, flag): + """If set, GladTeX will convert all non-ascii character to LaTeX + commands. This setting is passed through to typesetting.LaTeXDocument.""" + self.__replace_nonascii = flag + + + def convert_all(self, formulas): + """convert_all(formulas) + Convert all formulas using self.convert concurrently. Each element of + `formulas` must be a tuple containing (formula, displaymath, + Formulas already contained in the cache are not converted.""" + formulas_to_convert = self._get_formulas_to_convert(formulas) + if formulas_to_convert: + self.__converter = image.Tex2img(Format.Png + if self.__options['png'] else Format.Svg) + # apply configured image output options + for option, value in self.__options.items(): + if value and hasattr(self.__converter, 'set_' + option): + if isinstance(value, str): # only try string -> number + try: # some values are numbers + value = float(value) + except ValueError: + pass + getattr(self.__converter, 'set_' + option)(value) + self._convert_concurrently(formulas_to_convert) + + def _get_formulas_to_convert(self, formulas): + """Return a list of formulas to convert, along with their count in the + global list of formulas of the document being converted and the file + name. Function was decomposed for better testability.""" + formulas_to_convert = [] # find as many file names as equations + file_ext = (Format.Png.value if self.__options['png'] + else Format.Svg.value) + eqn_path = lambda x: os.path.join(self.__base_path, 'eqn%03d.%s' % (x, + file_ext)) + + # is (formula, display_math) already in the list of formulas to convert; + # displaymath is important since formulas look different in inline maths + formula_was_converted = lambda f, dsp: (normalize_formula(f), dsp) in \ + ((normalize_formula(u[0]), u[3]) for u in formulas_to_convert) + # find enough free file names + file_name_count = 0 + used_file_names = [] # track which file names have been assigned + for formula_count, (pos, dsp, formula) in enumerate(formulas): + if not self.__cache.contains(formula, dsp) and \ + not formula_was_converted(formula, dsp): + while os.path.exists(eqn_path(file_name_count)) or \ + eqn_path(file_name_count) in used_file_names: + file_name_count += 1 + used_file_names.append(eqn_path(file_name_count)) + formulas_to_convert.append((formula, pos, eqn_path(file_name_count), + dsp, formula_count + 1)) + return formulas_to_convert + + + def _convert_concurrently(self, formulas_to_convert): + """The actual concurrent conversion process. Method is intended to be + called from convert_all().""" + if self.__base_path and not os.path.exists(self.__base_path): + # create directory *before* it is required in the concurrent + # formulacreation step + os.makedirs(self.__base_path) + + thread_count = int(multiprocessing.cpu_count() * 2.5) + # convert missing formulas + with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor: + # start conversion and mark each thread with its formula, position + # in the source file and formula_count (index into a global list of + # formulas) + jobs = {executor.submit(self.__convert, eqn, path, dsp): (eqn, pos, count) + for (eqn, pos, path, dsp, count) in formulas_to_convert} + error_occurred = None + for future in concurrent.futures.as_completed(jobs): + if error_occurred and not future.done(): + future.cancel() + continue + formula, pos_in_src, formula_count = jobs[future] + try: + data = future.result() + except subprocess.SubprocessError as e: + # retrieve the position (line, pos on line) in the source + # document from original formula list + if pos_in_src: # missing for the pandocfilter case + pos_in_src = list(p+1 for p in pos_in_src) # user expects lines/pos_in_src' to count from 1 + self.__cache.write() # write back cache with valid entries + if not pos_in_src: # pandocfilter case: + error_occurred = ConversionException(str(e.args[0]), + formula, formula_count) + else: + error_occurred = ConversionException(str(e.args[0]), formula, + formula_count, pos_in_src[0], pos_in_src[1]) + else: + self.__cache.add_formula(formula, data['pos'], data['path'], + data['displaymath']) + self.__cache.write() + #pylint: disable=raising-bad-type + if error_occurred: + raise error_occurred + + + + def __convert(self, formula, output_path, displaymath=False): + """convert(formula, output_path, displaymath=False) + Convert given formula with displaymath/inlinemath. + This method wraps the formula in a tex document, executes all the steps + to produce a image and return the positioning information for the + HTML output. It does not check the cache. + :param formula formula to convert + :param output_path image output path + :param displaymath whether or not to use displaymath during the conversion + :return dictionary with position (pos), image path (path) and formula + style (displaymath, boolean) as a dictionary with the keys in + parenthesis""" + latex = typesetting.LaTeXDocument(formula) + latex.set_displaymath(displaymath) + def set(opt, setter): + if self.__options[opt]: + getattr(latex, 'set_' + setter)(self.__options[opt]) + set('preamble', 'preamble_string') + set('latex_maths_env', 'latex_environment') + set('background_color', 'background_color') + set('foreground_color', 'foreground_color') + + if self.__encoding: + latex.set_encoding(self.__encoding) + if self.__replace_nonascii: + latex.set_replace_nonascii(True) + # dvipng needs the additionalindication of transparency (enabled by + # default) when setting a background colour + if self.__options['background_color']: + self.__converter.set_transparency(False) + pos = self.__converter.convert(latex, + os.path.splitext(output_path)[0]) + return {'pos' : pos, 'path' : output_path, 'displaymath' : + displaymath} + + def get_data_for(self, formula, display_math): + """Simple wrapper around ImageCache, enriching the returned data with + the information provided as arguments to this function. This helps when + using a formula without its context.""" + data = self.__cache.get_data_for(formula, display_math).copy() + data.update({'formula': formula, 'displaymath': display_math}) + return data diff --git a/support/gladtex/gleetex/caching.py b/support/gladtex/gleetex/caching.py new file mode 100644 index 0000000000..9fbae2c924 --- /dev/null +++ b/support/gladtex/gleetex/caching.py @@ -0,0 +1,233 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""This module contains the ImageCache, caching formulas which have already been +converted. This allows to re-use images for formulas which occur multiple timesd +within a document. Furthermore, it can significantly speed up incremental +document creation, because the cache is remembered across GladTeX runs. + +Cache format: + + { # dict of formulas + 'some formula': # formula as key into dictionary + { # list of display math / inline maths variants + True: # displaymath = True + { # dictionary of values describing formula + 'path': 'some/path' + 'pos': { # positioning within the HTML document + 'height': ..., 'width':..., 'depth:.... + } + } + } + } + } + +Formulas are `normalized`, so spacing is unified to detect possibly equal +formulas more easyly. +""" + +import json +import os + +CACHE_VERSION = '2.0' + +def normalize_formula(formula): + """This function normalizes a formula. This e.g. means that multiple white + spaces are squeezed into one and a tab will be replaced by a space. With + this it is more realistic that a recurring formula in a document is detected + as such, even though if it might have been written with different spacing. + Empty braces ({}) are removed as well.""" + return formula.replace('{}', ' ').replace('\t', ' ').replace(' ', ' '). \ + rstrip().lstrip() + +def recover_bools(object): + """After JSon is read from disk, keys as False or True have been serialized + to 'false' and 'true', but they're not recovered by the json parser. This + function alters converts these keys back to booleans; note: it only works + with references, so this function doesn't return anything.""" + if isinstance(object, dict): + for key in ['false', 'true']: + if key in object: + val = object[key] # store value + object[key == 'true'] = val # safe it with boolean representation + del object[key] # remove string key + # iterate recursively through dict + for value in object.values(): + recover_bools(value) + if isinstance(object, list): + for item in object: + recover_bools(item) + +class JsonParserException(Exception): + """Specialized exception class for handling errors while parsing the JSON + cache.""" + pass + +class ImageCache: + """ + This cache stores formulas which have been converted already and don't need + to be converted again. This is both a disk usage and performance + improvement. The cache can be written and read from disk. + + If the argument keep_old_cache is True, the cache will raise a + JsonParserException if that file could not be read (i.e. incompatible + GladTeX version). If set to False, it'll discard the cache along with all + eqn* files and start with a clean cache. + + cache = ImageCache() + c.add_formula('\\tau', # the formulas + {'height': 1, 'depth': 2, 'width='3'}, # the positioning information for the output document + 'eqn042.png', displaymath=True): + assert len(cache) == 1 # one entry + c.write() + assert os.path.exists('gladtex.cache') + """ + VERSION_STR = 'GladTeX__cache__version' + + def __init__(self, path='gladtex.cache', keep_old_cache=True): + self.__cache = {} + self.__set_version(CACHE_VERSION) + self.__path = path + if os.path.exists(path): + try: + self._read() + except JsonParserException: + if keep_old_cache: + raise + else: + self._remove_old_cache_and_files() + + def __len__(self): + """Return number of formulas in the cache.""" + # ignore version + return len(self.__cache) - 1 + + def __set_version(self, version): + """Set version of cache (data structure format).""" + self.__cache[ImageCache.VERSION_STR] = version + + def write(self): + """Write cache to disk. The file name will be the one configured during + initialisation of the cache.""" + if len(self.__cache) == 0: + return + with open(self.__path, 'w', encoding='UTF-8') as file: + file.write(json.dumps(self.__cache)) + + def _read(self): + """Read Json from disk into cache, if file exists. + :raises JsonParserException if json could not be parsed""" + def raise_error(msg): + raise JsonParserException(msg + "\nPlease delete the cache (and" + \ + " the images) and rerun the program.") + if os.path.exists(self.__path): + #pylint: disable=broad-except + try: + with open(self.__path, 'r', encoding='utf-8') as file: + self.__cache = json.load(file) + except Exception as e: + msg = "error while reading cache from %s: " % os.path.abspath(self.__path) + if isinstance(e, (ValueError, OSError)): + msg += str(e.args[0]) + elif isinstance(e, UnicodeDecodeError): + msg += 'expected UTF-8 encoding, erroneous byte ' + \ + '{0} at {1}:{2} ({3})'.format(*(e.args[1:])) + else: + msg += str(e.args[0]) + raise_error(msg) + if not isinstance(self.__cache, dict): + raise_error("Decoded Json is not a dictionary.") + if not self.__cache.get(ImageCache.VERSION_STR): + self.__set_version(CACHE_VERSION) + cur_version = self.__cache.get(ImageCache.VERSION_STR) + if cur_version != CACHE_VERSION: + raise_error("Cache in %s has version %s, expected %s." % \ + (self.__path, cur_version, CACHE_VERSION)) + recover_bools(self.__cache) + + def _remove_old_cache_and_files(self): + os.remove(self.__path) + directory = os.path.split(self.__path)[0] + if not directory: + directory = '.' + # remove all files starting with eqn* + for file in os.listdir(directory): + if not file.startswith('eqn'): + continue + file = os.path.join(directory, file) + if os.path.isfile(file): + os.remove(file) + + def add_formula(self, formula, pos, file_path, displaymath=False): + """Add formula to cache. The pos argument contains the positioning + info for the output document and is a dict with 'height', 'width' and + 'depth'. + Keep in mind that formulas set with displaymath are not the same as + those set iwth inlinemath. + This method raises OSError if specified image doesn't exist or if it got + an absolute file_path.""" + if not pos or not formula or not file_path: + raise ValueError("the supplied arguments may not be empty/none") + if not isinstance(displaymath, bool): + raise ValueError("displaymath must be a boolean") + if os.path.isabs(file_path): + raise OSError(("The file path to the image may NOT be an absolute " + "path: ") + file_path) + if '\\' in file_path: + file_path = file_path.replace('\\', '/') + if not os.path.exists(file_path): + raise OSError("cannot add %s to the cache: doesn't exist" % + file_path) + formula = normalize_formula(formula) + if not formula in self.__cache: + self.__cache[formula] = {} + val = self.__cache[formula] + if not displaymath in val: + val[displaymath] = {'pos' : pos, 'path' : file_path} + + def remove_formula(self, formula, displaymath): + """This method removes the given formula from the cache. A KeyError is + raised, if the formula did not exist. Internally, formulas are + normalized to detect similarities.""" + formula = normalize_formula(formula) + if not formula in self.__cache: + raise KeyError("key %s not in cache" % formula) + else: + value = self.__cache[formula] + if displaymath in value: + del self.__cache[formula] + else: + raise KeyError("key %s (%s) not in cache" % (formula, displaymath)) + + def contains(self, formula, displaymath): + """Check whether a formula was already cached and return True if + found.""" + try: + return bool(self.get_data_for(formula, displaymath)) + except KeyError: + return False + + + def get_data_for(self, formula, displaymath): + """ + Retrieve meta data about a formula from the cache. + + The meta information is used to embed the formula in the HTML document. + It is a dictionary with the keys 'pos' and 'path'. The positioning info + is described in the documentation of this class. + This method raises a KeyError if the formula wasn't found.""" + formula = normalize_formula(formula) + if not formula in self.__cache: + raise KeyError(formula, displaymath) + else: + # check whether file still exists + value = self.__cache[formula] + if displaymath in value.keys(): + if not os.path.exists(value[displaymath]['path']): + del self.__cache[formula] + raise KeyError((formula, displaymath)) + else: + return value[displaymath] + else: + raise KeyError((formula, displaymath)) + diff --git a/support/gladtex/gleetex/htmlhandling.py b/support/gladtex/gleetex/htmlhandling.py new file mode 100644 index 0000000000..458de3142c --- /dev/null +++ b/support/gladtex/gleetex/htmlhandling.py @@ -0,0 +1,485 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""GleeTeX is designed to allow the re-use of the image creation code +independently of the HTML conversion code. Therefore, this modules contains the +code required to parse equations from HTML, to write converted HTML documents +back and to handle the outsourcing of formulas too long for an HTML alt tag to +an external file. +The pandoc module contains similar functions for using GleeTeX as a pandoc +filter.""" + +import collections +import enum +import html.parser +import os +import posixpath +import re + +from . import typesetting + +# match HTML 4 and 5 +CHARSET_PATTERN = re.compile( + rb'(?:content="text/html; charset=(.*?)"|charset="(.*?)")') + +class ParseException(Exception): + """Exception to propagate a parsing error.""" + def __init__(self, msg, pos=None): + self.msg = msg + self.pos = pos + super().__init__(msg, pos) + + def __str__(self): + return ('line {0.pos[0]}, {0.pos[1]}: {0.msg}'.format(self) + if self.pos else self.msg) + +def get_position(document, index): + """This returns the line number and position on line for the given String. + Note: lines and positions are counted from 0.""" + line = document[:index+1].count('\n') + if document[index] == '\n': + return (line, 0) + newline = document[:index+1].rfind('\n') + newline = (newline if newline >= 0 else 0) + return (line, len(document[newline:index])) + + +def find_anycase(where, what): + """Find with both lower or upper case.""" + lower = where.find(what.lower()) + upper = where.find(what.upper()) + if lower >= 0: + return lower + else: + return upper + +class EqnParser: + """This parser parses <eq>...</eq> our of a document. It's not an HTML + parser, because the content within <eq>.*<eq> is parsed verbatim. + It also parses comments, to not consider formulas within comments. All other + cases are unhandled. Especially CData is problematic, although it seems like + a rare use case.""" + class State(enum.Enum): # ([\s\S]*?) also matches newlines + Comment = re.compile(r'<!--([\s\S]*?)-->', re.MULTILINE) + Equation = re.compile(r'<\s*(?:eq|EQ)\s*(.*?)?>([\s\S.]+?)<\s*/\s*(?:eq|EQ)>', + re.MULTILINE) + + HTML_ENTITY = re.compile(r'(&(:?#\d+|[a-zA-Z]+);)') + + def __init__(self): + self.__document = None + self.__data = [] + self.__encoding = None + + def feed(self, document): + """Feed a string or a bytes instance and start parsing. If a bytes + instance is fed, an HTML encoding header has to be present, so that the + encoding can be extracted.""" + if isinstance(document, bytes): # try to guess encoding + try: + encoding = next(filter(bool, CHARSET_PATTERN.search(document) + .groups())).decode('ascii') + document = document.decode(encoding) + except AttributeError: + raise ParseException(("Could not determine encoding of " + "document, no charset information in the HTML header " + "found.")) + self.__encoding = encoding + self.__document = document[:] + self._parse() + + def find_with_offset(self, doc, start, what): + """This find method searches in the document for a given string, staking + the offset into account. REturned is the absolute position (so offset + + relative match position) or -1 for no hit.""" + if isinstance(what, str): + pos = doc[start:].find(what) + else: + match = what.search(doc[start:]) + pos = (-1 if not match else match.span()[0]) + return (pos if pos == -1 else pos + start) + + + def _parse(self): + """This function parses the document, while maintaining state using the + State enum.""" + in_document = lambda x: (False if x == -1 else True) + # maintain a lower-case copy, which eases searching, but doesn't affect + # the handler methods + doc = self.__document[:].lower() + + end = len(self.__document) - 1 + eq_start = re.compile(r'<\s*eq\s*(.*?)>') + + start_pos = 0 + while start_pos < end: + comment = self.find_with_offset(doc, start_pos, '<!--') + formula = self.find_with_offset(doc, start_pos, eq_start) + if in_document(comment) and in_document(formula): # both present, take closest + if comment < formula: + self.__data.append(self.__document[start_pos:comment]) + start_pos = self.handle_comment(comment) + else: + self.__data.append(self.__document[start_pos:formula]) + start_pos = self.handle_equation(formula) + elif in_document(formula): + self.__data.append(self.__document[start_pos:formula]) + start_pos = self.handle_equation(formula) + elif in_document(comment): + self.__data.append(self.__document[start_pos:comment]) + start_pos = self.handle_comment(comment) + else: # only data left + self.__data.append(self.__document[start_pos:]) + start_pos = end + + + def handle_equation(self, start_pos): + """Parse an equation. The given offset should mark the beginning of this + equation.""" + # get line and column of `start_pos` + lnum, pos = get_position(self.__document, start_pos) + + match = EqnParser.State.Equation.value.search(self.__document[start_pos:]) + if not match: + next_eq = find_anycase(self.__document[start_pos+1:], '<eq') + closing = find_anycase(self.__document[start_pos:], '</eq>') + if next_eq > -1 and closing > -1 and next_eq < closing: + raise ParseException("Unclosed tag found", (lnum, pos)) + else: + raise ParseException("Malformed equation tag found", (lnum, pos)) + end = start_pos + match.span()[1] + attrs, formula = match.groups() + if '<eq>' in formula or '<EQ' in formula: + raise ParseException("Invalid nesting of formulas detected.", (lnum, + pos)) + + # replace HTML entities + entity = EqnParser.HTML_ENTITY.search(formula) + while entity: + formula = re.sub(EqnParser.HTML_ENTITY, + html.unescape(entity.groups()[0]), formula) + entity = EqnParser.HTML_ENTITY.search(formula) + attrs = attrs.lower() + displaymath = (True if attrs and 'env' in attrs and 'displaymath' in attrs + else False) + self.__data.append(((lnum, pos), # let line number count from 0 as well + displaymath, formula)) + return end + + + def handle_comment(self, start_pos): + match = EqnParser.State.Comment.value.search(self.__document[start_pos:]) + if not match: + lnum, pos = get_position(self.__document, start_pos) + # this could be a parser issue, too + raise ParseException("Improperly formatted comment found", (lnum, + pos)) + self.__data.append('<!--%s-->' % match.groups()[0]) + return start_pos + match.span()[1] # return end of match + + def get_encoding(self): + """Return the parsed encoding from the HTML meta data. If none was set, + UTF-8 is assumed.""" + return self.__encoding + + def get_data(self): + """Return parsed chunks. These are either strings or tuples with formula + information, see class documentation.""" + return list(x for x in self.__data if x) # filter empty bits + + +def gen_id(formula): + """Generate an id for identifying a formula as an anchor in a document. + The generated ID is guaranteed to be valid in an XML attribute and it won't + exceed a certain length. + If you happen to have a lot of formulas > 150 characters with exactly + the same content in the document, that'll cause a clash of id's.""" + # for some characters we just use a simple replacement (otherwise the + # would be lost) + mapped = {'{':'_', '}':'_', '(':'-', ')':'-', '\\':'.', '^':',', '*':'_'} + id = [] + prevchar = '' + for c in formula: + if prevchar == c: + continue # avoid multiple same characters + if c in mapped: + id.append(mapped[c]) + elif c.isalpha() or c.isdigit(): + id.append(c) + prevchar = c + # id's must start with an alphabetical character, so prefix the formula with + # "formula" to make it a valid html id + if id and not id[0].isalpha(): + id = ['f', 'o', 'r', 'm', '_'] + id + if not id: # is empty + raise ValueError("For the formula '%s' no referencable id could be generated." \ + % formula) + return ''.join(id[:150]) + + +class OutsourcedFormulaParser(html.parser.HTMLParser): + """This HTML parser parses the head and tries to keep it close to the + original document as possible. As soon as a formula is encountered, only + the formulas are parsed. Everything in between and after the them will be + fully ignored. + A sample formula would look like: + + <p id="id_as_generated_by_gen_id"><pre>stuff</pre></p> + """ + def __init__(self): + self.__head = [] + self.__id = None + self.__passed_head = False + self.__equations = collections.OrderedDict() + super().__init__(convert_charrefs=False) + + def handle_starttag(self, tag, attrs): + if tag == 'p': + attrs = dict(attrs) + if attrs.get('id'): + self.__id = attrs['id'] # marks beginning of a formula paragraph + self.__equations[self.__id] = '' + return + elif tag == 'body': + self.__passed_head = True + self.__head.append('\n<body>\n') + if not self.__passed_head: + self.__head.append(self.get_starttag_text()) + + def handle_startendtag(self, tag, attrs): + if self.__id or self.__passed_head: return # skip everything inside a formula + if attrs: + self.__head.append('<{} {} />'.format(tag, + ' '.join(['%s="%s"' % (x[0], x[1]) for x in attrs]))) + else: + self.__head.append('<%s />' % tag) + + def handle_endtag(self, tag): + if self.__id: # inside a formula + if tag == 'p': + self.__id = None # end formula block + elif not self.__passed_head: + formatted = '</%s>' % tag + self.__head.append(formatted) + + def handle_data(self, data): + if self.__id: + self.__equations[self.__id] += data + elif not self.__passed_head: + self.__head.append(data) + + def handle_entityref(self, name): + if self.__id: + self.__equations[self.__id] += '&%s;' % name + elif not self.__passed_head: + self.__head.append('&%s;' % name) + + def handle_charref(self, name): + if self.__id: + self.__equations[self.__id] += '&#%s;' % name + elif not self.__passed_head: + self.__head.append('&#%s;' % name) + + def handle_comment(self, blah): + if not self.__passed_head: + self.__head.append('<!--%s-->' % blah) + + def handle_decl(self, declaration): + if not self.__passed_head: + self.__head.append('<!%s>' % declaration) + + + def get_head(self): + """Return a string containing everything before the first formula.""" + return ''.join(self.__head) + + def get_formulas(self): + """Return an ordered dictionary with id : formula paragraph.""" + return self.__equations + + def error(self, message): + raise ParseException(message, ('unknown', 'unknown')) + +def format_formula_paragraph(formula): + """Format a formula to appear as if it would have been outsourced into an + external file.""" + return '<p id="%s"><pre>%s</pre></span></p>\n' % \ + (gen_id(formula), formula) + + +class HtmlImageFormatter: # ToDo: localisation + """HtmlImageFormatter(exclusion_filepath='outsourced_formulas.html', + encoding="UTF-8") + Format converted formula to be included into the HTML. A typical image + attribute will contain the path to the image, style information, a CSS class + to be used in custom CSS style sheets and an alternative text (the LaTeX + source) for people who disabled images or for blind screen reader users. + If set, LaTeX formulas exceeding a configurable maximum length will be + excluded. The image will be a link which leads to the excluded image text. + The alt attribute is a text-only attribute and e.g. line breaks will be lost + for screen reader users, so it makes sense for longer formulas to be + external to be easily readable. Furthermore the alt attribute is limited to + 255 characters, so formula blocks exceeding that limit need to be treated + differently anyway. If that behavior is not wanted, it can be disabled and + nothing will be excluded.""" + + EXCLUSION_FILE_NAME = 'outsourced-descriptions.html' + HTML_TEMPLATE_HEAD = ('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"' + + '\n "http://www.w3.org/TR/html4/strict.dtd">\n<html>\n<head>\n' + + '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>' + + '\n<title>Outsourced Formulas</title>\n</head>\n<!-- ' + + 'DO NOT MODIFY THIS FILE, IT IS AUTOMATICALLY GENERATED -->\n<body>\n') + def __init__(self, base_path='', link_prefix=None): + self.__exclude_descriptions = False + self.__link_prefix = (link_prefix if link_prefix else '') + self.__base_path = (base_path if base_path else '') + self.__exclusion_filepath = posixpath.join(self.__base_path, HtmlImageFormatter.EXCLUSION_FILE_NAME) + if os.path.exists(self.__exclusion_filepath): + if not os.access(self.__exclusion_filepath, os.W_OK): + raise OSError('The file %s is not writable!' % + self.__exclusion_filepath) + self.__inline_maxlength=100 + self.__file_head = HtmlImageFormatter.HTML_TEMPLATE_HEAD + self.__cached_formula_pars = collections.OrderedDict() + self.__url = '' + self.initialized = False + self.initialize() # read already written file, if any + self.__css = {'inline' : 'inlinemath', 'display' : 'displaymath'} + self.__replace_nonascii = False + + def set_replace_nonascii(self, flag): + """If True, non-ascii characters will be replaced through their LaTeX + command. Note that alphabetical characters will not be replaced, to + allow easier readibility.""" + self.__replace_nonascii = flag + + def set_max_formula_length(self, length): + """Set maximum length of a formula before it gets outsourced into a + separate file.""" + self.__inline_maxlength = length + + def set_inline_math_css_class(self, css): + """set css class for inline math.""" + self.__css['inline'] = css + + def set_display_math_css_class(self, css): + """set css class for display math.""" + self.__css['display'] = css + + def set_exclude_long_formulas(self, flag): + """When set, the LaTeX code of a formula longer than the configured + maxlength will be excluded and written + linked into a separate file.""" + self.__exclude_descriptions = flag + + def set_url(self, prefix): + """Set URL prefix which is used as a prefix to the image file in the + HTML link.""" + self.__url = prefix + + def initialize(self): + """Initialize the image writer. If a file with already written image + descriptions exists, this one will be parsed first and new formulas + appended to it. Otherwise a new file will be written upon ending the + with-resources block.""" + if self.initialized: + return + self.initialized = True + if not os.path.exists(self.__exclusion_filepath): + return self + document = None + with open(self.__exclusion_filepath, 'r', encoding='UTF-8') as f: + document = f.read() + # parse html document: + parser = OutsourcedFormulaParser() + parser.feed(document) + self.__file_head = parser.get_head() + self.__cached_formula_pars = parser.get_formulas() + return self + + def __enter__(self): + return self + + def __exit__(self, useless, unused, not_applicable): + self.close() + + def close(self): + """Write back file with excluded image descriptions, if any.""" + def formula2paragraph(frml): + return '<p id="%s"><pre>%s</pre></p>' % (gen_id(frml), frml) + if not self.__cached_formula_pars: + return + with open(self.__exclusion_filepath, 'w', encoding='utf-8') as f: + f.write(self.__file_head) + f.write('\n<hr />\n'.join([formula2paragraph(formula) \ + for formula in self.__cached_formula_pars.values()])) + f.write('\n</body>\n</html>\n') + + def get_html_img(self, pos, formula, img_path, displaymath=False): + """:param pos dictionary containing keys depth, height and width + :param formula LaTeX alternative text + :param img_path: path to image + :param displaymath display or inline math (default False, inline maths) + :returns a string with the formatted HTML""" + full_url = img_path + if self.__url: + if self.__url.endswith('/'): self.__url = self.__url[:-1] + full_url = self.__url + '/' + img_path + # depth is a negative offset + depth = float(pos['depth']) * -1 + css = (self.__css['display'] if displaymath else self.__css['inline']) + return ('<img src="{0}" style="vertical-align: {3:.2f}px; margin: 0;" ' + 'height="{2[height]:.2f}" width="{2[width]:.2f}" alt="{1}" ' + 'class="{4}" />').format(full_url, formula, pos, depth, css) + + def format_excluded(self, pos, formula, img_path, displaymath=False): + """This method formats a formula and an formula image in HTML and + additionally writes the formula to an external (configured) file to + which the image links to. That's useful for blind screen reader users + who can then easily have a look at the formula. + :param pos dictionary containing keys depth, height and width + :param formula LaTeX alternative text + :param img_path: path to image + :param displaymath if set to true, image is treated as display math formula (default False) + :returns string with formatted HTML image which also links to excluded + formula""" + shortened = (formula[:100] + '...' if len(formula) > 100 else formula) + img = self.get_html_img(pos, shortened, img_path, displaymath) + identifier = gen_id(formula) + # write formula out to external file + if identifier not in self.__cached_formula_pars: + self.__cached_formula_pars[identifier] = formula + exclusion_filelink = posixpath.join(self.__link_prefix, self.__exclusion_filepath) + return '<a href="{}#{}">{}</a>'.format(exclusion_filelink, + gen_id(formula), img) + + def format(self, pos, formula, img_path, displaymath=False): + """This method formats a formula. If self.__exclude_descriptions is set + and the formula igreater than the configured length, the formula will be + outsourced, otherwise it'll be included in the IMG's alt tag. In either + case, a string for the current document containing the formatted HTML is returned. + :param pos dictionary containing keys depth, height and width + :param formula LaTeX alternative text + :param img_path: path to image + :param displaymath whether or not formula is in display math (default: no) + :returns string with formatted HTML image which also links to excluded + formula""" + formula = typesetting.increase_readability(formula, + self.__replace_nonascii) + if self.__exclude_descriptions and \ + len(formula) > self.__inline_maxlength: + return self.format_excluded(pos, formula, img_path, displaymath) + return self.get_html_img(pos, formula, img_path, displaymath) + +def write_html(file, document, formatter): + """Processed HTML documents are made up of raw HTML chunks which are written + back unaltered and of processed image. An processed image is a former + formula converted to an image with additional meta data. This is passed to + the format function of the supplied formatter and the result is written to + the given (open) file handle.""" + for chunk in document: + if isinstance(chunk, dict): + is_displaymath = chunk['displaymath'] + file.write(formatter.format(chunk['pos'], chunk['formula'], + chunk['path'], is_displaymath)) + else: + file.write(chunk) diff --git a/support/gladtex/gleetex/image.py b/support/gladtex/gleetex/image.py new file mode 100644 index 0000000000..12c92761ad --- /dev/null +++ b/support/gladtex/gleetex/image.py @@ -0,0 +1,293 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""This module takes care of the actual image creation process. + +Each formula is saved as an image, either as PNG or SVG. SVG is advised, since +it is a properly scalable format. +""" + +import enum +import os +import re +import shutil +import subprocess +import sys + +from .typesetting import LaTeXDocument + +DVIPNG_REGEX = re.compile(r"^ depth=(-?\d+) height=(\d+) width=(\d+)") +DVISVGM_DEPTH_REGEX = re.compile(r"^\s*width=.*?pt, height=.*?pt, depth=(.*?)pt") +DVISVGM_SIZE_REGEX = re.compile(r"^\s*graphic size: (.*?)pt x (.*?)pt") + +def remove_all(*files): + """Guarded remove of files (rm -f); no exception is thrown if a file + couldn't be removed.""" + for file in files: + try: + os.remove(file) + except OSError: + pass + + +def proc_call(cmd, cwd=None, install_recommends=True): + """Execute cmd (list of arguments) as a subprocess. Returned is a tuple with + stdout and stderr, decoded if not None. If the return value is not equal 0, a + subprocess error is raised. Timeouts will happen after 20 seconds.""" + with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + bufsize=1, universal_newlines=False, cwd=cwd) as proc: + data = [] + try: + data = [d.decode(sys.getdefaultencoding(), errors="surrogateescape") + for d in proc.communicate(timeout=20) if d] + if proc.wait(): + raise subprocess.SubprocessError("Error while executing %s\n%s\n" % + (' '.join(cmd), '\n'.join(data))) + except subprocess.TimeoutExpired as e: + proc.kill() + note = 'Subprocess expired with time out: ' + str(cmd) + '\n' + poll = proc.poll() + if poll: + note += str(poll) + '\n' + if data: + raise subprocess.SubprocessError(str(data + '\n' + note)) + else: + raise subprocess.SubprocessError('execution timed out after ' + + str(e.args[1]) + ' s: ' + ' '.join(e.args[0])) + except KeyboardInterrupt as e: + sys.stderr.write("\nInterrupted; ") + import traceback + traceback.print_exc(file=sys.stderr) + except FileNotFoundError: + # program missing, try to help + text = "Command `%s` not found." % cmd[0] + if install_recommends and shutil.which('dpkg'): + text += ' Install it using `sudo apt install ' + install_recommends + else: + text += ' Install a TeX distribution of your choice, e.g. MikTeX or TeXlive.' + raise subprocess.SubprocessError(text) from None + if isinstance(data, list): + return '\n'.join(data) + return data + +#pylint: disable=too-few-public-methods +class Format(enum.Enum): + """Chose the image output format.""" + Png = 'png' + Svg = 'svg' + +class Tex2img: + """Convert a TeX document string into a png file. + This class interacts with the LaTeX and dvipng sub processes. Upon error + the methods throw a SubprocessError with all necessary information to fix + the issue. + + The background of the PNG files will be transparent by default. If you set a + background colour within the LaTeX document, you need to turn off + transparency in this converter manually.""" + def __init__(self, fmt, encoding="UTF-8"): + if not isinstance(fmt, Format): + raise ValueError("Enumeration of type Format expected."+str(fmt)) + self.__format = fmt + self.__encoding = encoding + self.__parsed_data = None + self.__size = [115, None] + self.__background = 'transparent' + self.__keep_latex_source = False + + def set_dpi(self, dpi): + """Set output resolution for formula images. This has no effect ifthe + output format is SVG. It will automatically overwrite a font size, if + set.""" + if not isinstance(dpi, (int, float)): + raise TypeError("Dpi must be an integer or floating point number") + self.__size[0] = int(dpi) + + def set_fontsize(self, size): + """Set font size for formulas. This will be automatically translated + into a DPI resolution for PNG images and taken literally for SVG + graphics.""" + if not isinstance(size, (int, float)): + raise TypeError("Dpi must be an integer or floating point number") + self.__size[1] = float(size) + + def set_transparency(self, flag): + """Set whether or not to use background colour information from the DVI + file. This is only relevant for PNG output and if a background colour + other than "transparent" is required, in this case this set'r should be + set to false. It is set to True, resulting in a transparent + background.""" + self.__background = ('transparent' if flag else 'not transparent') + def set_keep_latex_source(self, flag): + """Set whether LaTeX source document should be kept.""" + if not isinstance(flag, bool): + raise TypeError("boolean object required, got %s." % repr(flag)) + self.__keep_latex_source = flag + + + def create_dvi(self, tex_document, dvi_fn): + """Call LaTeX to produce a dvi file with the given LaTeX document. + Temporary files will be removed, even in the case of a LaTeX error. + This method raises a SubprocessError with the helpful part of LaTeX's + error output.""" + path = os.path.dirname(dvi_fn) + if path and not os.path.exists(path): + os.makedirs(path) + if not path: + path = os.getcwd() + new_extension = lambda x: os.path.splitext(dvi_fn)[0] + '.' + x + + if self.__size[1]: # font size in pt + tex_document.set_fontsize(self.__size[1]) + tex_fn = new_extension('tex') + aux_fn = new_extension('aux') + log_fn = new_extension('log') + cmd = None + encoding = self.__encoding + with open(tex_fn, mode='w', encoding=encoding) as tex: + tex.write(str(tex_document)) + cmd = ['latex', '-halt-on-error', os.path.basename(tex_fn)] + try: + proc_call(cmd, cwd=path, install_recommends='texlive-recommended') + except subprocess.SubprocessError as e: + remove_all(dvi_fn) + msg = '' + if e.args: + data = self.parse_latex_log(e.args[0]) + if data: + msg += data + else: + msg += str(e.args[0]) + raise subprocess.SubprocessError(msg) # propagate subprocess error + finally: + if self.__keep_latex_source: + remove_all(aux_fn, log_fn) + else: + remove_all(tex_fn, aux_fn, log_fn) + + def create_image(self, dvi_fn): + """Create the image containing the formula, using either dvisvgm or + dvipng.""" + dirname = os.path.dirname(dvi_fn) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + + output_fn = '%s.%s' % (os.path.splitext(dvi_fn)[0], self.__format.value) + if self.__format == Format.Png: + dpi = (fontsize2dpi(self.__size[1]) if self.__size[1] + else self.__size[0]) + return create_png(dvi_fn, output_fn,dpi, + self.__background) + if not self.__size[1]: + self.__size[1] = 12 # 12 pt + return create_svg(dvi_fn, output_fn) + + def convert(self, tex_document, base_name): + """Convert the given TeX document into an image. The base name is used + to create the required intermediate files and the resulting file will be + made of the base_name and the format-specific file extension. + This function returns the positioning information used in the CSS style + attribute.""" + if not isinstance(tex_document, LaTeXDocument): + raise TypeError(("expected object of type typesetting.LaTeXDocument," + " got %s") % type(tex_document)) + dvi = '%s.dvi' % base_name + try: + self.create_dvi(tex_document, dvi) + return self.create_image(dvi) + except OSError: + remove_all('%s.%s' % (base_name, self.__format.value)) + raise + + def parse_latex_log(self, logdata): + """Parse the LaTeX error output and return the relevant part of it.""" + if not logdata: + return None + line = None + for line in logdata.split('\n'): + if line.startswith('! '): + line = line[2:] + break + if line: # try to remove LaTeX line numbers + lineno = re.search(r'\s*on input line \d+', line) + if lineno: + line = line[:lineno.span()[0]] + line[lineno.span()[1]:] + return line + return None + +def fontsize2dpi(size_pt): + """This function calculates the DPI for the resulting image. Depending on + the font size, a different resolution needs to be used. According to the + dvipng manual page, the formula is: + <dpi> = <font_px> * 72.27 / 10 [px * TeXpt/in / TeXpt]""" + size_px = size_pt * 1.3333333 # and more 3s! + return size_px * 72.27 / 10 + +def create_png(dvi_fn, output_name, dpi, background): + """Create a PNG file from a given dvi file. The side effect is the PNG file + being written to disk. + By default, the background of the resulting image is transparent, setting + any other value will make it use whatever was is set in the DVI file. + :param dvi_fn Dvi file name + :param output_name Output file name + :param dpi Output resolution + :param background Background colour (default: transparent) + :return dimensions for embedding into an HTML document + :raises ValueError raised whenever dvipng output coudln't be parsed""" + if not output_name: + raise ValueError("Empty output_name") + cmd = ['dvipng', '-q*', '-D', str(dpi)] + if background == 'transparent': + cmd += ['-bg', background] + cmd += ['--height*', '--depth*', '--width*', # print information for embedding + '-o', output_name, dvi_fn] + data = None + try: + data = proc_call(cmd, install_recommends='dvipng') + except subprocess.SubprocessError: + remove_all(output_name) + raise + finally: + remove_all(dvi_fn) + for line in data.split('\n'): + found = DVIPNG_REGEX.search(line) + if found: + return dict(zip(['depth', 'height', 'width'], + map(float, found.groups()))) + raise ValueError("Could not parse dvi output: " + repr(data)) + +def create_svg(dvi_fn, output_name): + """Create a SVG file from a given dvi file. The side effect is the SVG file + being written to disk. + :param dvi_fn Dvi file name + :param output_name Output file name + :param size font size in pt + :return dimensions for embedding into an HTML document + :raises ValueError raised whenever dvipng output coudln't be parsed""" + if not output_name: + raise ValueError("Empty output_name") + cmd = ['dvisvgm', '--exact', '--no-fonts', '-o', output_name, + '--bbox=preview', dvi_fn] + data = None + try: + data = proc_call(cmd, install_recommends='texlive-binaries') + except subprocess.SubprocessError: + remove_all(output_name) + raise + finally: + remove_all(dvi_fn) + pos = {} + for line in data.split('\n'): + if not pos: + found = DVISVGM_DEPTH_REGEX.search(line) + if found: + # convert from pt to px (assuming 96 dpi) + pos['depth'] = float(found.groups()[0]) * 1.3333333 + else: + found = DVISVGM_SIZE_REGEX.search(line) + if found: + pos.update(dict(zip(['width', 'height'], + # convert from pt to px (assuming 96 dpi) + (float(v) * 1.3333333 for v in found.groups())))) + return pos + raise ValueError("Could not parse dvisvgm output: " + repr(data)) diff --git a/support/gladtex/gleetex/pandoc.py b/support/gladtex/gleetex/pandoc.py new file mode 100644 index 0000000000..44fa9bbfb9 --- /dev/null +++ b/support/gladtex/gleetex/pandoc.py @@ -0,0 +1,75 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""This module contains functionality to parse formulas from a given Pandoc +document AST and to replace these through formatted HTML equations. Even +though this could be done in a single run, this would conflict with the internal +GleeTeX structure and allows for an easy parallelisation of the formula +conversion.""" + +import json + +from .htmlhandling import ParseException + +def __extract_formulas(formulas, ast): + """Recursively extract 'Math' elements from the given AST and add them to + `formulas (list)`.""" + if isinstance(ast, list): + for item in ast: + __extract_formulas(formulas, item) + elif isinstance(ast, dict): + if 't' in ast and ast['t'] == 'Math': + style, formula = ast['c'] + # style = {'t': 'blah'} -> we want blah + style = next(iter(style.values())) + if style not in ['InlineMath', 'DisplayMath']: + raise ParseException("[pandoc] unknown formula formatting: " + \ + repr(ast['c'])) + style = (True if style == 'DisplayMath' else False) + # position is None (only applicable for HTML parsing) + formulas.append((None, style, formula)) + elif 'c' in ast: + __extract_formulas(formulas, ast['c']) + # ^ all other cases do not matter + +def extract_formulas(ast): + """Extract formulas from a given Pandoc document AST. + The returned formulas are typed like those form the HTML parser, therefore + the first argument of the tuple is unused and hence None. + :param ast Structure of lists and dicts representing a Pandoc document AST + :return a list of formulas where each formula is (None, style, formula)""" + formulas = [] + __extract_formulas(formulas, ast['blocks']) + return formulas + +def replace_formulas_in_ast(formatter, ast, formulas): + """replace 'Math' elements from the given AST with a formatted variant + Each 'Math' element found in the Pandoc AST will be replaced through a + formatted (HTML) image link. The formulas are taken from the supplied + formulas list. The number of formulas in the document has to match the + number of formulas form the list.""" + if not formulas: + return + if isinstance(ast, list): + for item in ast: + replace_formulas_in_ast(formatter, item, formulas) + elif isinstance(ast, dict): + if 't' in ast and ast['t'] == 'Math': + ast['t'] = 'RawInline' # raw HTML + eqn = formulas.pop(0) + ast['c'] = ['html',formatter.format(eqn['pos'], eqn['formula'], eqn['path'], + eqn['displaymath'])] + elif 'c' in ast: + replace_formulas_in_ast(formatter, ast['c'], formulas) + # ^ ignore all other cases + +def write_pandoc_ast(file, document, formatter): + """Replace 'Math' elements from a Pandoc AST with 'RawInline' elements, + containing formatted HTML image tags. + :param formatter A formatter offering the "format" method (see ImageFormatter) + :param formulas A list of formulas with the information (pos, formula, path, displaymath) + :param ast Document ast to modified""" + ast, formulas = document + replace_formulas_in_ast(formatter, ast['blocks'], formulas) + file.write(json.dumps(ast)) + diff --git a/support/gladtex/gleetex/parser.py b/support/gladtex/gleetex/parser.py new file mode 100644 index 0000000000..dba08451ed --- /dev/null +++ b/support/gladtex/gleetex/parser.py @@ -0,0 +1,64 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""Top-level API to parse input documents. + +The main point of the parsing is to extract formulas from a given input +document, while preserving the remaining formatting. +The returned parsed document structure is highly dependent on the input format +and hence document in their respective functions.""" + +import enum +import json +import sys + +from . import htmlhandling +from . import pandoc +ParseException = htmlhandling.ParseException # re-export for consistent API from outside + +class Format(enum.Enum): + HTML = 0 + # while this is json, we never know what other applications might decide to + # use json as their intermediate representation ;) + PANDOCFILTER = 1 + + @staticmethod + def parse(string): + string = string.lower() + if string == 'html': + return Format.HTML + elif string == 'pandocfilter': + return Format.PANDOCFILTER + else: + raise ValueError("unrecognised format: %s" % string) + +def parse_document(doc, fmt): + """This function parses an input document (string or bytes) with the given + format specifier. For HTML, the returned "parsed" document is a list of + chunks, where raw chunks are just plain HTML instructions and data and + formula chunks are parsed from the '<eq/>' tags. + If the input document is a pandoc AST, the formulas will be extracted and + the document is a tuple of (pandoc AST, formulas). + + :param doc input of bytes or string to parse + :param fmt either the enum type `Format` or a string understood by Format.parse + :return (encoding, document) (a tuple)""" + if isinstance(fmt, str): + fmt = Format.parse(fmt) + encoding = None + if fmt == Format.HTML: + docparser = htmlhandling.EqnParser() + docparser.feed(doc) + encoding = docparser.get_encoding() + encoding = (encoding if encoding else 'utf-8') + doc = docparser.get_data() + elif fmt == Format.PANDOCFILTER: + if isinstance(doc, bytes): + doc = doc.decode(sys.getdefaultencoding()) + ast = json.loads(doc) + formulas = pandoc.extract_formulas(ast) + doc = (ast, formulas) # ← see doc string + if not encoding: + encoding = sys.getdefaultencoding() + return encoding, doc + diff --git a/support/gladtex/gleetex/typesetting.py b/support/gladtex/gleetex/typesetting.py new file mode 100644 index 0000000000..c55cab5b12 --- /dev/null +++ b/support/gladtex/gleetex/typesetting.py @@ -0,0 +1,331 @@ +# (c) 2013-2018 Sebastian Humenda +# This code is licenced under the terms of the LGPL-3+, see the file COPYING for +# more details. +"""This module contains functionality to typeset formuas for the usage in a +LaTeX document (e.g. creating the preamble, replacing non-ascii letters) and to +typeset LaTeX formulas in a more readable way as alternate description of the +resulting image. """ + +from . import unicode + +FORMATTING_COMMANDS = ['\\ ', '\\,', '\\;', '\\big', '\\Big', '\\left', + '\\right', '\\limits'] + + + +class DocumentSerializationException(Exception): + """This error is raised whenever a non-ascii character contained in a + formula could not be replaced by a LaTeX command. + It provides the following attributes: + formula - the formula + index - position in formula + upoint - unicode point.""" + def __init__(self, formula, index, upoint): + self.formula = formula + self.index = index + self.upoint = upoint + super().__init__(formula, index, upoint) + + def __str__(self): + return ("could not find LaTeX replacement command for unicode " + "character %d, index %d in formula %s") % (self.upoint, + self.index, self.formula) + + +def escape_unicode_maths(formula, replace_alphabeticals=True): + """This function uses the unicode table to replace any non-ascii character + (identified with its unicode code point) with a LaTeX command. + It also parses the formula for commands as e.g. \\\text or \\mbox and + applies text-mode commands within them. + This allows the conversion of formulas with unicode maths with old-style + LaTeX2e, which gleetex depends on.""" + if not any(ord(ch) > 160 for ch in formula): + return formula # no umlauts, no replacement + + # characters in math mode need a different replacement than in text mode. + # Therefore, the string has to be split into parts of math and text mode. + chunks = [] + if not ('\\text' in formula or '\\mbox' in formula): + # no text mode, so tread a + chunks = [formula] + else: + start = 0 + while '\\text' in formula[start:] or '\\mbox' in formula[start:]: + index = formula[start:].find('\\text') + if index < 0: + index = formula[start:].find('\\mbox') + opening_brace = formula[start + index:].find('{') + start + index + # add text before text-alike command and the command itself to chunks + chunks.append(formula[start:opening_brace]) + closing_brace = get_matching_brace(formula, opening_brace) + # add text-mode stuff + chunks.append(formula[opening_brace:closing_brace + 1]) + start = closing_brace + 1 + # add last chunk + chunks.append(formula[start:]) + + is_math = True + for index, chunk in enumerate(chunks): + try: + chunks[index] = replace_unicode_characters(chunk, is_math, + replace_alphabeticals=replace_alphabeticals) + except ValueError as e: # unicode point missing + index = int(e.args[0]) + raise DocumentSerializationException(formula, index, + ord(formula[index])) from None + is_math = not is_math + return ''.join(chunks) + + +def replace_unicode_characters(characters, is_math, replace_alphabeticals=True): + """Replace all non-ascii characters within the given string with their LaTeX + equivalent. The boolean is_math indicates, whether text-mode commands (like + in \\text{}) or the amsmath equivalents should be used. + When replace_alphabeticals is False, alphabetical characters will not be + replaced through their LaTeX command when in text mode, so that text within + \\text{} (and similar) is not garbled. For instance, \\text{für} is be + replaced by \\text{f\"{u}r} when replace_alphabeticals=True. This is useful + for the alt attribute of an image, where the reader might want to read + the normal text as such. + This function raises a ValueError if a unicode point is not in the table. + The first argument of the ValueError is the index within the string, where + the unknown unicode character has been encountered.""" + result = [] + for idx, character in enumerate(characters): + if ord(character) < 168: # ignore normal ascii character and unicode control sequences + result.append(character) + # treat alphanumerical characters differently when in text mode, see doc + # string; don't replace alphabeticals if specified + elif character.isalpha() and not replace_alphabeticals: + result.append(character) + else: + mode = (unicode.LaTeXMode.mathmode if is_math else + unicode.LaTeXMode.textmode) + commands = unicode.unicode_table.get(ord(character)) + if not commands: # unicode point missing in table + # is catched one level above; provide index for more concise error output + raise ValueError(characters.index(character)) + # if math mode and only a text alternative exists, add \\text{} + # around it + if mode == unicode.LaTeXMode.mathmode and mode not in commands: + result.append('\\text{%s}' % commands[unicode.LaTeXMode.textmode]) + else: + result.append(commands[mode]) + # if the next character is alphabetical, add space + if (idx+1) < len(characters) and characters[idx+1].isalpha() \ + and commands[mode][-1].isalpha(): + result.append(' ') + return ''.join(result) + +def get_matching_brace(string, pos_of_opening_brace): + if string[pos_of_opening_brace] != '{': + raise ValueError("index %s in string %s: not a opening brace" % \ + (pos_of_opening_brace, repr(string))) + counter = 1 + for index, ch in enumerate(string[pos_of_opening_brace + 1:]): + if ch == '{': + counter += 1 + elif ch == '}': + counter -= 1 + if counter == 0: + return pos_of_opening_brace + index + 1 + if counter != 0: + raise ValueError("Unbalanced braces in formula " + repr(string)) + + + +#pylint: disable=too-many-instance-attributes +class LaTeXDocument: + """This class represents a LaTeX document. It is intended to contain an + equation as main content and properties to customize it. Its main purpose is + to provide a str method which will serialize it to a full LaTeX document. + """ + def __init__(self, eqn): + self.__encoding = None + self.__equation = eqn + self.__displaymath = False + self.__fontsize = 12 + self.__background_color = None + self.__foreground_color = None + self._preamble = '' + self.__maths_env = None + self.__replace_nonascii = False + + def _parse_color(self, color): + # could be a valid color name + try: # hex number? + return int(color, 16) + except ValueError: + return color # treat as normal dvips compatible colour name + + def set_background_color(self, color): + """Set the background color. The `color` can be either a valid dvips + name or a tuple with RGB values between 0 and 1. If unset, the image + will be transparent.""" + self.__background_color = self._parse_color(color) + + def set_foreground_color(self, color): + """Set the foreground color. The `color` can be either a valid dvips + name or a tuple with RGB values between 0 and 1. If unset, the text + will be black.""" + self.__foreground_color = self._parse_color(color) + + def set_replace_nonascii(self, flag): + """If True, all non-ascii character will be replaced through a LaTeX + command.""" + self.__replace_nonascii = flag + + def set_latex_environment(self, env): + """Set maths environment name like `displaymath` or `flalign*`.""" + self.__maths_env = env + + def get_latex_environment(self): + return self.__maths_env + + def get_encoding(self): + """Return encoding for the document (or None).""" + return self.__encoding + + def set_preamble_string(self, p): + """Set the string to add to the preamble of the LaTeX document.""" + self._preamble = p + + def set_encoding(self, encoding): + """Set the encoding as used by the inputenc package.""" + if encoding.lower().startswith('utf') and '8' in encoding: + self.__encoding = 'utf8' + elif (encoding.lower().startswith('iso') and '8859' in encoding) or \ + encoding.lower() == 'latin1': + self.__encoding = 'latin1' + else: + # if you plan to add an encoding, you have to adjust the str + # function, which also loads the fontenc package + raise ValueError(("Encoding %s is not supported at the moment. If " + "you want to use LaTeX 2e, you should report a bug at the home " + "page of GladTeX.") % encoding) + + def set_displaymath(self, flag): + """Set whether the formula is set in displaymath.""" + if not isinstance(flag, bool): + raise TypeError("Displaymath parameter must be of type bool.") + self.__displaymath = flag + + def is_displaymath(self): + return self.__displaymath + + def _get_encoding_preamble(self): + # first check whether there are umlauts within the formula and if so, an + # encoding has been set + if any(ord(ch) > 128 for ch in self.__equation) and \ + not self.__replace_nonascii: + if not self.__encoding: + raise ValueError(("No encoding set, but non-ascii characters " + "present. Please specify an encoding.")) + encoding_preamble = '' + if self.__encoding: + # try to guess language and hence character set (fontenc) + import locale + language = locale.getdefaultlocale() + if language and language[0]: # extract just the language code + language = language[0].split('_')[0] + if not language or not language[0]: + language = 'en' + # check whether language on computer is within T1 and hence whether + # it should be loaded; I know that this can be a misleading + # assumption, but there's no better way that I know of + if language in ['fr', 'es', 'it', 'de', 'nl', 'ro', 'en']: + encoding_preamble += '\n\\usepackage[T1]{fontenc}' + else: + raise ValueError(("Language not supported by T1 fontenc " + "encoding; please report this to the GladTeX project.")) + return encoding_preamble + + def set_fontsize(self, size_in_pt): + """Set fontsize in pt, 12 pt by default.""" + self.__fontsize = size_in_pt + + def get_fontsize(self, size_in_pt): + return self.__fontsize + + def __str__(self): + preamble = self._get_encoding_preamble() + \ + ('\n\\usepackage[utf8]{inputenc}\n\\usepackage{amsmath, amssymb}' + '\n') + (self._preamble if self._preamble else '') + return self._format_document(preamble) + + def _format_color_definition(self, which): + color = getattr(self, '_%s__%s_color' % (self.__class__.__name__, + which)) + if not color or isinstance(color, str): + return '' + return ('\\definecolor{%s}{HTML}{%s}' % (which, + hex(color)[2:].upper().zfill(6))) + + def _format_colors(self): + color_defs = (self._format_color_definition('background'), + self._format_color_definition('foreground'),) + color_body = '' + if self.__background_color: + color_body += ('\\pagecolor{%s}' % ('background' if color_defs[0] + else self.__background_color)) + if self.__foreground_color: + # opening brace isn't required here, inserted automatically + color_body += ('\\color{%s}' % ('foreground' if color_defs[1] + else self.__foreground_color)) + return (''.join(color_defs), color_body) + + + def _format_document(self, preamble): + """Return a formatted LaTeX document with the specified formula + embedded.""" + opening, closing = None,None + if self.__maths_env: + opening = '\\begin{%s}' % self.__maths_env + closing = '\\end{%s}' % self.__maths_env + else: + # determine characters with which to surround the formula + opening = '\\[' if self.__displaymath else '\\(' + closing = '\\]' if self.__displaymath else '\\)' + formula = self.__equation.lstrip().rstrip() + if self.__replace_nonascii: + formula = escape_unicode_maths(formula, replace_alphabeticals=True) + fontsize = 'fontsize=%ipt' % self.__fontsize + color_preamble, color_body = self._format_colors() + return ("\\documentclass[%s, fleqn]{scrartcl}\n\n%s\n" + "\\usepackage[dvipsnames]{xcolor}\n" + "%s\n" # color definitions, if applicable + "\\usepackage[active,textmath,displaymath,tightpage]{preview} " + "%% must be last one, see doc\n\n\\begin{document}\n" + "\\noindent%%\n" + "\\begin{preview}{%s" + "%s%s%s}\\end{preview}\n" + "\\end{document}\n") % (fontsize, preamble, color_preamble, + color_body, opening, formula, closing) + + +def increase_readability(formula, replace_nonascii=False): + """In alternate texts for non-image users or those using a screen reader, + the LaTeX code should be as readable as possible. Therefore the formula + should not contain unicode characters or formatting instructions.""" + if replace_nonascii: + # keep umlauts, etc; makes the alt more readable, yet wouldn't compile + formula = escape_unicode_maths(formula, replace_alphabeticals=False) + # replace formatting-only symbols which distract the reader + formula_changed = True + while formula_changed: + formula_changed = False + for command in FORMATTING_COMMANDS: + idx = formula.find(command) + # only replace if it's not after a \\ and not part of a longer command + if (idx > 0 and formula[idx-1] != '\\') or idx == 0: + end = idx + len(command) + # following conditions for replacement must be met: + # command doesn't end on alphabet. char. and is followed by same + # category OR end of string reached OR command does not # end on + # alphabetical char. at all + if end >= len(formula) or not command[-1].isalpha() \ + or not formula[end].isalpha(): + formula = formula[:idx] + ' ' + formula[idx + len(command):] + formula = formula.replace(' ', ' ') + formula_changed = True + return formula diff --git a/support/gladtex/gleetex/unicode.py b/support/gladtex/gleetex/unicode.py new file mode 100644 index 0000000000..f6a6fbd8f8 --- /dev/null +++ b/support/gladtex/gleetex/unicode.py @@ -0,0 +1,2390 @@ +""" +DO NOT ALTER THIS FILE IN ANY WAY, IT IS GENERATED AUTOMATICALLY. SEE THE SCRIPT +`update_unicode_table.py` FOR MORE INFORMATION. + +This file contains a table of unicode code point to LaTeX command mapping. It +has 2369 entries and was derived from +<https://raw.githubusercontent.com/w3c/xml-entities/gh-pages/unicode.xml>.""" +#pylint: disable=too-many-lines,missing-docstring + + +import enum + +class LaTeXMode(enum.Enum): + """Represent either math or text mode. Math mode in LaTeX is e.g. + everything between $ and $.""" + mathmode = 1 + textmode = 0 + +unicode_table = { + 161: {LaTeXMode.textmode: '\\textexclamdown ', LaTeXMode.mathmode: '\\textexclamdown'}, + 162: {LaTeXMode.textmode: '\\textcent ', LaTeXMode.mathmode: '\\mbox{\\textcent}'}, + 163: {LaTeXMode.textmode: '\\textsterling ', LaTeXMode.mathmode: '\\sterling'}, + 164: {LaTeXMode.textmode: '\\textcurrency ', LaTeXMode.mathmode: '\\mbox{\\textcurrency}'}, + 165: {LaTeXMode.textmode: '\\textyen ', LaTeXMode.mathmode: '\\yen'}, + 166: {LaTeXMode.textmode: '\\textbrokenbar '}, + 167: {LaTeXMode.textmode: '\\textsection ', LaTeXMode.mathmode: '\\S'}, + 168: {LaTeXMode.textmode: '\\textasciidieresis '}, + 169: {LaTeXMode.textmode: '\\textcopyright ', LaTeXMode.mathmode: '\\copyright'}, + 170: {LaTeXMode.textmode: '\\textordfeminine '}, + 171: {LaTeXMode.textmode: '\\guillemotleft ', LaTeXMode.mathmode: '\\mbox{\\guillemotleft}'}, + 172: {LaTeXMode.textmode: '\\lnot ', LaTeXMode.mathmode: '\\neg'}, + 173: {LaTeXMode.textmode: '\\-'}, + 174: {LaTeXMode.textmode: '\\textregistered ', LaTeXMode.mathmode: '\\circledR'}, + 175: {LaTeXMode.textmode: '\\textasciimacron '}, + 176: {LaTeXMode.textmode: '\\textdegree ', LaTeXMode.mathmode: '\\degree'}, + 177: {LaTeXMode.textmode: '\\pm ', LaTeXMode.mathmode: '\\pm'}, + 178: {LaTeXMode.textmode: '{^2}', LaTeXMode.mathmode: '^2'}, + 179: {LaTeXMode.textmode: '{^3}', LaTeXMode.mathmode: '^3'}, + 180: {LaTeXMode.textmode: '\\textasciiacute ', LaTeXMode.mathmode: '\\textasciiacute'}, + 181: {LaTeXMode.textmode: '\\mathrm{\\mu}', LaTeXMode.mathmode: '\\mu'}, + 182: {LaTeXMode.textmode: '\\textparagraph ', LaTeXMode.mathmode: '\\P'}, + 183: {LaTeXMode.textmode: '\\cdot ', LaTeXMode.mathmode: '\\cdotp'}, + 184: {LaTeXMode.textmode: '\\c{}', LaTeXMode.mathmode: '\\mbox{\\c{}}'}, + 185: {LaTeXMode.textmode: '{^1}', LaTeXMode.mathmode: '^1'}, + 186: {LaTeXMode.textmode: '\\textordmasculine '}, + 187: {LaTeXMode.textmode: '\\guillemotright ', LaTeXMode.mathmode: '\\mbox{\\guillemotright}'}, + 188: {LaTeXMode.textmode: '\\textonequarter '}, + 189: {LaTeXMode.textmode: '\\textonehalf '}, + 190: {LaTeXMode.textmode: '\\textthreequarters '}, + 191: {LaTeXMode.textmode: '\\textquestiondown ', LaTeXMode.mathmode: '\\textquestiondown'}, + 192: {LaTeXMode.textmode: '\\`{A}', LaTeXMode.mathmode: '\\grave{A}'}, + 193: {LaTeXMode.textmode: "\\'{A}", LaTeXMode.mathmode: '\\acute{A}'}, + 194: {LaTeXMode.textmode: '\\^{A}', LaTeXMode.mathmode: '\\hat{A}'}, + 195: {LaTeXMode.textmode: '\\~{A}', LaTeXMode.mathmode: '\\tilde{A}'}, + 196: {LaTeXMode.textmode: '\\"{A}', LaTeXMode.mathmode: '\\ddot{A}'}, + 197: {LaTeXMode.textmode: '\\AA '}, + 198: {LaTeXMode.textmode: '\\AE ', LaTeXMode.mathmode: '\\AE'}, + 199: {LaTeXMode.textmode: '\\c{C}', LaTeXMode.mathmode: '\\mbox{\\c{C}}'}, + 200: {LaTeXMode.textmode: '\\`{E}', LaTeXMode.mathmode: '\\grave{E}'}, + 201: {LaTeXMode.textmode: "\\'{E}", LaTeXMode.mathmode: '\\acute{E}'}, + 202: {LaTeXMode.textmode: '\\^{E}', LaTeXMode.mathmode: '\\hat{E}'}, + 203: {LaTeXMode.textmode: '\\"{E}', LaTeXMode.mathmode: '\\ddot{E}'}, + 204: {LaTeXMode.textmode: '\\`{I}', LaTeXMode.mathmode: '\\grave{I}'}, + 205: {LaTeXMode.textmode: "\\'{I}", LaTeXMode.mathmode: '\\acute{I}'}, + 206: {LaTeXMode.textmode: '\\^{I}', LaTeXMode.mathmode: '\\hat{I}'}, + 207: {LaTeXMode.textmode: '\\"{I}', LaTeXMode.mathmode: '\\ddot{I}'}, + 208: {LaTeXMode.textmode: '\\DH '}, + 209: {LaTeXMode.textmode: '\\~{N}', LaTeXMode.mathmode: '\\tilde{N}'}, + 210: {LaTeXMode.textmode: '\\`{O}', LaTeXMode.mathmode: '\\grave{O}'}, + 211: {LaTeXMode.textmode: "\\'{O}", LaTeXMode.mathmode: '\\acute{O}'}, + 212: {LaTeXMode.textmode: '\\^{O}', LaTeXMode.mathmode: '\\hat{O}'}, + 213: {LaTeXMode.textmode: '\\~{O}', LaTeXMode.mathmode: '\\tilde{O}'}, + 214: {LaTeXMode.textmode: '\\"{O}', LaTeXMode.mathmode: '\\ddot{O}'}, + 215: {LaTeXMode.textmode: '\\texttimes ', LaTeXMode.mathmode: '\\times'}, + 216: {LaTeXMode.textmode: '\\O ', LaTeXMode.mathmode: '\\O'}, + 217: {LaTeXMode.textmode: '\\`{U}', LaTeXMode.mathmode: '\\grave{U}'}, + 218: {LaTeXMode.textmode: "\\'{U}", LaTeXMode.mathmode: '\\acute{U}'}, + 219: {LaTeXMode.textmode: '\\^{U}', LaTeXMode.mathmode: '\\hat{U}'}, + 220: {LaTeXMode.textmode: '\\"{U}', LaTeXMode.mathmode: '\\ddot{U}'}, + 221: {LaTeXMode.textmode: "\\'{Y}", LaTeXMode.mathmode: '\\acute{Y}'}, + 222: {LaTeXMode.textmode: '\\TH '}, + 223: {LaTeXMode.textmode: '\\ss ', LaTeXMode.mathmode: '\\ss'}, + 224: {LaTeXMode.textmode: '\\`{a}', LaTeXMode.mathmode: '\\grave{a}'}, + 225: {LaTeXMode.textmode: "\\'{a}", LaTeXMode.mathmode: '\\acute{a}'}, + 226: {LaTeXMode.textmode: '\\^{a}', LaTeXMode.mathmode: '\\hat{a}'}, + 227: {LaTeXMode.textmode: '\\~{a}', LaTeXMode.mathmode: '\\tilde{a}'}, + 228: {LaTeXMode.textmode: '\\"{a}', LaTeXMode.mathmode: '\\ddot{a}'}, + 229: {LaTeXMode.textmode: '\\aa '}, + 230: {LaTeXMode.textmode: '\\ae ', LaTeXMode.mathmode: '\\ae'}, + 231: {LaTeXMode.textmode: '\\c{c}', LaTeXMode.mathmode: '\\mbox{\\c{c}}'}, + 232: {LaTeXMode.textmode: '\\`{e}', LaTeXMode.mathmode: '\\grave{e}'}, + 233: {LaTeXMode.textmode: "\\'{e}", LaTeXMode.mathmode: '\\acute{e}'}, + 234: {LaTeXMode.textmode: '\\^{e}', LaTeXMode.mathmode: '\\hat{e}'}, + 235: {LaTeXMode.textmode: '\\"{e}', LaTeXMode.mathmode: '\\ddot{e}'}, + 236: {LaTeXMode.textmode: '\\`{\\i}', LaTeXMode.mathmode: '\\grave{\\imath}'}, + 237: {LaTeXMode.textmode: "\\'{\\i}", LaTeXMode.mathmode: '\\acute{\\imath}'}, + 238: {LaTeXMode.textmode: '\\^{\\i}', LaTeXMode.mathmode: '\\hat{\\imath}'}, + 239: {LaTeXMode.textmode: '\\"{\\i}', LaTeXMode.mathmode: '\\ddot{\\imath}'}, + 240: {LaTeXMode.textmode: '\\dh ', LaTeXMode.mathmode: '\\eth'}, + 241: {LaTeXMode.textmode: '\\~{n}', LaTeXMode.mathmode: '\\tilde{n}'}, + 242: {LaTeXMode.textmode: '\\`{o}', LaTeXMode.mathmode: '\\grave{o}'}, + 243: {LaTeXMode.textmode: "\\'{o}", LaTeXMode.mathmode: '\\acute{o}'}, + 244: {LaTeXMode.textmode: '\\^{o}', LaTeXMode.mathmode: '\\hat{o}'}, + 245: {LaTeXMode.textmode: '\\~{o}', LaTeXMode.mathmode: '\\tilde{o}'}, + 246: {LaTeXMode.textmode: '\\"{o}', LaTeXMode.mathmode: '\\ddot{o}'}, + 247: {LaTeXMode.textmode: '\\div ', LaTeXMode.mathmode: '\\div'}, + 248: {LaTeXMode.textmode: '\\o ', LaTeXMode.mathmode: '\\o'}, + 249: {LaTeXMode.textmode: '\\`{u}', LaTeXMode.mathmode: '\\grave{u}'}, + 250: {LaTeXMode.textmode: "\\'{u}", LaTeXMode.mathmode: '\\acute{u}'}, + 251: {LaTeXMode.textmode: '\\^{u}', LaTeXMode.mathmode: '\\hat{u}'}, + 252: {LaTeXMode.textmode: '\\"{u}', LaTeXMode.mathmode: '\\ddot{u}'}, + 253: {LaTeXMode.textmode: "\\'{y}", LaTeXMode.mathmode: '\\acute{y}'}, + 254: {LaTeXMode.textmode: '\\th '}, + 255: {LaTeXMode.textmode: '\\"{y}', LaTeXMode.mathmode: '\\ddot{y}'}, + 256: {LaTeXMode.textmode: '\\={A}', LaTeXMode.mathmode: '\\bar{A}'}, + 257: {LaTeXMode.textmode: '\\={a}', LaTeXMode.mathmode: '\\bar{a}'}, + 258: {LaTeXMode.textmode: '\\u{A}', LaTeXMode.mathmode: '\\breve{A}'}, + 259: {LaTeXMode.textmode: '\\u{a}', LaTeXMode.mathmode: '\\u{a}'}, + 260: {LaTeXMode.textmode: '\\k{A}'}, + 261: {LaTeXMode.textmode: '\\k{a}'}, + 262: {LaTeXMode.textmode: "\\'{C}", LaTeXMode.mathmode: '\\acute{C}'}, + 263: {LaTeXMode.textmode: "\\'{c}", LaTeXMode.mathmode: '\\acute{c}'}, + 264: {LaTeXMode.textmode: '\\^{C}', LaTeXMode.mathmode: '\\hat{C}'}, + 265: {LaTeXMode.textmode: '\\^{c}', LaTeXMode.mathmode: '\\hat{c}'}, + 266: {LaTeXMode.textmode: '\\.{C}', LaTeXMode.mathmode: '\\dot{C}'}, + 267: {LaTeXMode.textmode: '\\.{c}', LaTeXMode.mathmode: '\\dot{c}'}, + 268: {LaTeXMode.textmode: '\\v{C}', LaTeXMode.mathmode: '\\check{C}'}, + 269: {LaTeXMode.textmode: '\\v{c}', LaTeXMode.mathmode: '\\check{c}'}, + 270: {LaTeXMode.textmode: '\\v{D}', LaTeXMode.mathmode: '\\check{D}'}, + 271: {LaTeXMode.textmode: '\\v{d}', LaTeXMode.mathmode: '\\check{d}'}, + 272: {LaTeXMode.textmode: '\\DJ '}, + 273: {LaTeXMode.textmode: '\\dj '}, + 274: {LaTeXMode.textmode: '\\={E}', LaTeXMode.mathmode: '\\bar{E}'}, + 275: {LaTeXMode.textmode: '\\={e}', LaTeXMode.mathmode: '\\bar{e}'}, + 276: {LaTeXMode.textmode: '\\u{E}', LaTeXMode.mathmode: '\\breve{E}'}, + 277: {LaTeXMode.textmode: '\\u{e}', LaTeXMode.mathmode: '\\breve{e}'}, + 278: {LaTeXMode.textmode: '\\.{E}', LaTeXMode.mathmode: '\\dot{E}'}, + 279: {LaTeXMode.textmode: '\\.{e}', LaTeXMode.mathmode: '\\dot{e}'}, + 280: {LaTeXMode.textmode: '\\k{E}', LaTeXMode.mathmode: '\\k{E}'}, + 281: {LaTeXMode.textmode: '\\k{e}'}, + 282: {LaTeXMode.textmode: '\\v{E}', LaTeXMode.mathmode: '\\check{E}'}, + 283: {LaTeXMode.textmode: '\\v{e}', LaTeXMode.mathmode: '\\check{e}'}, + 284: {LaTeXMode.textmode: '\\^{G}', LaTeXMode.mathmode: '\\hat{G}'}, + 285: {LaTeXMode.textmode: '\\^{g}', LaTeXMode.mathmode: '\\hat{g}'}, + 286: {LaTeXMode.textmode: '\\u{G}', LaTeXMode.mathmode: '\\breve{G}'}, + 287: {LaTeXMode.textmode: '\\u{g}', LaTeXMode.mathmode: '\\breve{g}'}, + 288: {LaTeXMode.textmode: '\\.{G}', LaTeXMode.mathmode: '\\dot{G}'}, + 289: {LaTeXMode.textmode: '\\.{g}', LaTeXMode.mathmode: '\\dot{g}'}, + 290: {LaTeXMode.textmode: '\\c{G}', LaTeXMode.mathmode: '\\mbox{\\c{G}}'}, + 291: {LaTeXMode.textmode: '\\c{g}', LaTeXMode.mathmode: '\\mbox{\\c{g}}'}, + 292: {LaTeXMode.textmode: '\\^{H}', LaTeXMode.mathmode: '\\hat{H}'}, + 293: {LaTeXMode.textmode: '\\^{h}', LaTeXMode.mathmode: '\\hat{h}'}, + 294: {LaTeXMode.textmode: '{\\fontencoding{LELA}\\selectfont\\char40}'}, + 295: {LaTeXMode.textmode: '\\Elzxh '}, + 296: {LaTeXMode.textmode: '\\~{I}', LaTeXMode.mathmode: '\\tilde{I}'}, + 297: {LaTeXMode.textmode: '\\~{\\i}', LaTeXMode.mathmode: '\\tilde{\\imath}'}, + 298: {LaTeXMode.textmode: '\\={I}', LaTeXMode.mathmode: '\\bar{I}'}, + 299: {LaTeXMode.textmode: '\\={\\i}', LaTeXMode.mathmode: '\\bar{\\imath}'}, + 300: {LaTeXMode.textmode: '\\u{I}', LaTeXMode.mathmode: '\\breve{I}'}, + 301: {LaTeXMode.textmode: '\\u{\\i}', LaTeXMode.mathmode: '\\breve{\\imath}'}, + 302: {LaTeXMode.textmode: '\\k{I}'}, + 303: {LaTeXMode.textmode: '\\k{i}'}, + 304: {LaTeXMode.textmode: '\\.{I}', LaTeXMode.mathmode: '\\dot{I}'}, + 305: {LaTeXMode.textmode: '\\i ', LaTeXMode.mathmode: '\\imath'}, + 306: {LaTeXMode.textmode: 'IJ'}, + 307: {LaTeXMode.textmode: 'ij'}, + 308: {LaTeXMode.textmode: '\\^{J}', LaTeXMode.mathmode: '\\hat{J}'}, + 309: {LaTeXMode.textmode: '\\^{\\j}', LaTeXMode.mathmode: '\\hat{\\jmath}'}, + 310: {LaTeXMode.textmode: '\\c{K}', LaTeXMode.mathmode: '\\mbox{\\c{K}}'}, + 311: {LaTeXMode.textmode: '\\c{k}', LaTeXMode.mathmode: '\\mbox{\\c{k}}'}, + 312: {LaTeXMode.textmode: '{\\fontencoding{LELA}\\selectfont\\char91}'}, + 313: {LaTeXMode.textmode: "\\'{L}", LaTeXMode.mathmode: '\\acute{L}'}, + 314: {LaTeXMode.textmode: "\\'{l}", LaTeXMode.mathmode: '\\acute{l}'}, + 315: {LaTeXMode.textmode: '\\c{L}', LaTeXMode.mathmode: '\\mbox{\\c{L}}'}, + 316: {LaTeXMode.textmode: '\\c{l}', LaTeXMode.mathmode: '\\mbox{\\c{l}}'}, + 317: {LaTeXMode.textmode: '\\v{L}', LaTeXMode.mathmode: '\\check{L}'}, + 318: {LaTeXMode.textmode: '\\v{l}', LaTeXMode.mathmode: '\\check{l}'}, + 319: {LaTeXMode.textmode: '{\\fontencoding{LELA}\\selectfont\\char201}'}, + 320: {LaTeXMode.textmode: '{\\fontencoding{LELA}\\selectfont\\char202}'}, + 321: {LaTeXMode.textmode: '\\L ', LaTeXMode.mathmode: '\\L'}, + 322: {LaTeXMode.textmode: '\\l ', LaTeXMode.mathmode: '\\l'}, + 323: {LaTeXMode.textmode: "\\'{N}", LaTeXMode.mathmode: '\\acute{N}'}, + 324: {LaTeXMode.textmode: "\\'{n}", LaTeXMode.mathmode: '\\acute{n}'}, + 325: {LaTeXMode.textmode: '\\c{N}', LaTeXMode.mathmode: '\\mbox{\\c{N}}'}, + 326: {LaTeXMode.textmode: '\\c{n}', LaTeXMode.mathmode: '\\mbox{\\c{n}}'}, + 327: {LaTeXMode.textmode: '\\v{N}', LaTeXMode.mathmode: '\\check{N}'}, + 328: {LaTeXMode.textmode: '\\v{n}', LaTeXMode.mathmode: '\\check{n}'}, + 329: {LaTeXMode.textmode: "'n"}, + 330: {LaTeXMode.textmode: '\\NG '}, + 331: {LaTeXMode.textmode: '\\ng '}, + 332: {LaTeXMode.textmode: '\\={O}', LaTeXMode.mathmode: '\\bar{O}'}, + 333: {LaTeXMode.textmode: '\\={o}', LaTeXMode.mathmode: '\\bar{o}'}, + 334: {LaTeXMode.textmode: '\\u{O}', LaTeXMode.mathmode: '\\breve{O}'}, + 335: {LaTeXMode.textmode: '\\u{o}', LaTeXMode.mathmode: '\\breve{o}'}, + 336: {LaTeXMode.textmode: '\\H{O}', LaTeXMode.mathmode: '\\mbox{\\H{O}}'}, + 337: {LaTeXMode.textmode: '\\H{o}', LaTeXMode.mathmode: '\\mbox{\\H{o}}'}, + 338: {LaTeXMode.textmode: '\\OE ', LaTeXMode.mathmode: '\\OE'}, + 339: {LaTeXMode.textmode: '\\oe ', LaTeXMode.mathmode: '\\oe'}, + 340: {LaTeXMode.textmode: "\\'{R}", LaTeXMode.mathmode: '\\acute{R}'}, + 341: {LaTeXMode.textmode: "\\'{r}", LaTeXMode.mathmode: '\\acute{r}'}, + 342: {LaTeXMode.textmode: '\\c{R}', LaTeXMode.mathmode: '\\mbox{\\c{R}}'}, + 343: {LaTeXMode.textmode: '\\c{r}', LaTeXMode.mathmode: '\\mbox{\\c{r}}'}, + 344: {LaTeXMode.textmode: '\\v{R}', LaTeXMode.mathmode: '\\check{R}'}, + 345: {LaTeXMode.textmode: '\\v{r}', LaTeXMode.mathmode: '\\check{r}'}, + 346: {LaTeXMode.textmode: "\\'{S}", LaTeXMode.mathmode: '\\acute{S}'}, + 347: {LaTeXMode.textmode: "\\'{s}", LaTeXMode.mathmode: '\\acute{s}'}, + 348: {LaTeXMode.textmode: '\\^{S}', LaTeXMode.mathmode: '\\hat{S}'}, + 349: {LaTeXMode.textmode: '\\^{s}', LaTeXMode.mathmode: '\\hat{s}'}, + 350: {LaTeXMode.textmode: '\\c{S}', LaTeXMode.mathmode: '\\mbox{\\c{S}}'}, + 351: {LaTeXMode.textmode: '\\c{s}', LaTeXMode.mathmode: '\\mbox{\\c{s}}'}, + 352: {LaTeXMode.textmode: '\\v{S}', LaTeXMode.mathmode: '\\check{S}'}, + 353: {LaTeXMode.textmode: '\\v{s}', LaTeXMode.mathmode: '\\check{s}'}, + 354: {LaTeXMode.textmode: '\\c{T}', LaTeXMode.mathmode: '\\mbox{\\c{T}}'}, + 355: {LaTeXMode.textmode: '\\c{t}', LaTeXMode.mathmode: '\\mbox{\\c{t}}'}, + 356: {LaTeXMode.textmode: '\\v{T}', LaTeXMode.mathmode: '\\check{T}'}, + 357: {LaTeXMode.textmode: '\\v{t}', LaTeXMode.mathmode: '\\check{t}'}, + 358: {LaTeXMode.textmode: '{\\fontencoding{LELA}\\selectfont\\char47}'}, + 359: {LaTeXMode.textmode: '{\\fontencoding{LELA}\\selectfont\\char63}'}, + 360: {LaTeXMode.textmode: '\\~{U}', LaTeXMode.mathmode: '\\tilde{U}'}, + 361: {LaTeXMode.textmode: '\\~{u}', LaTeXMode.mathmode: '\\tilde{u}'}, + 362: {LaTeXMode.textmode: '\\={U}', LaTeXMode.mathmode: '\\bar{U}'}, + 363: {LaTeXMode.textmode: '\\={u}', LaTeXMode.mathmode: '\\bar{u}'}, + 364: {LaTeXMode.textmode: '\\u{U}', LaTeXMode.mathmode: '\\breve{U}'}, + 365: {LaTeXMode.textmode: '\\u{u}', LaTeXMode.mathmode: '\\breve{u}'}, + 366: {LaTeXMode.textmode: '\\r{U}', LaTeXMode.mathmode: '\\mathring{U}'}, + 367: {LaTeXMode.textmode: '\\r{u}', LaTeXMode.mathmode: '\\mathring{u}'}, + 368: {LaTeXMode.textmode: '\\H{U}', LaTeXMode.mathmode: '\\mbox{\\H{U}}'}, + 369: {LaTeXMode.textmode: '\\H{u}', LaTeXMode.mathmode: '\\mbox{\\H{u}}'}, + 370: {LaTeXMode.textmode: '\\k{U}', LaTeXMode.mathmode: '\\k{U}'}, + 371: {LaTeXMode.textmode: '\\k{u}'}, + 372: {LaTeXMode.textmode: '\\^{W}', LaTeXMode.mathmode: '\\hat{W}'}, + 373: {LaTeXMode.textmode: '\\^{w}', LaTeXMode.mathmode: '\\hat{w}'}, + 374: {LaTeXMode.textmode: '\\^{Y}', LaTeXMode.mathmode: '\\hat{Y}'}, + 375: {LaTeXMode.textmode: '\\^{y}', LaTeXMode.mathmode: '\\hat{y}'}, + 376: {LaTeXMode.textmode: '\\"{Y}', LaTeXMode.mathmode: '\\ddot{Y}'}, + 377: {LaTeXMode.textmode: "\\'{Z}", LaTeXMode.mathmode: '\\acute{Z}'}, + 378: {LaTeXMode.textmode: "\\'{z}", LaTeXMode.mathmode: '\\acute{z}'}, + 379: {LaTeXMode.textmode: '\\.{Z}', LaTeXMode.mathmode: '\\dot{Z}'}, + 380: {LaTeXMode.textmode: '\\.{z}', LaTeXMode.mathmode: '\\dot{z}'}, + 381: {LaTeXMode.textmode: '\\v{Z}', LaTeXMode.mathmode: '\\check{Z}'}, + 382: {LaTeXMode.textmode: '\\v{z}', LaTeXMode.mathmode: '\\check{z}'}, + 402: {LaTeXMode.textmode: 'f'}, + 405: {LaTeXMode.textmode: '\\texthvlig '}, + 414: {LaTeXMode.textmode: '\\textnrleg '}, + 426: {LaTeXMode.textmode: '\\eth '}, + 450: {LaTeXMode.textmode: '\\textdoublepipe '}, + 501: {LaTeXMode.textmode: "\\'{g}", LaTeXMode.mathmode: '\\acute{g}'}, + 592: {LaTeXMode.textmode: '\\Elztrna '}, + 594: {LaTeXMode.textmode: '\\Elztrnsa '}, + 596: {LaTeXMode.textmode: '\\Elzopeno '}, + 598: {LaTeXMode.textmode: '\\Elzrtld '}, + 601: {LaTeXMode.textmode: '\\Elzschwa '}, + 603: {LaTeXMode.textmode: '\\varepsilon '}, + 609: {LaTeXMode.textmode: 'g'}, + 611: {LaTeXMode.textmode: '\\Elzpgamma '}, + 612: {LaTeXMode.textmode: '\\Elzpbgam '}, + 613: {LaTeXMode.textmode: '\\Elztrnh '}, + 620: {LaTeXMode.textmode: '\\Elzbtdl '}, + 621: {LaTeXMode.textmode: '\\Elzrtll '}, + 623: {LaTeXMode.textmode: '\\Elztrnm '}, + 624: {LaTeXMode.textmode: '\\Elztrnmlr '}, + 625: {LaTeXMode.textmode: '\\Elzltlmr '}, + 627: {LaTeXMode.textmode: '\\Elzrtln '}, + 631: {LaTeXMode.textmode: '\\Elzclomeg '}, + 632: {LaTeXMode.textmode: '\\textphi '}, + 633: {LaTeXMode.textmode: '\\Elztrnr '}, + 634: {LaTeXMode.textmode: '\\Elztrnrl '}, + 635: {LaTeXMode.textmode: '\\Elzrttrnr '}, + 636: {LaTeXMode.textmode: '\\Elzrl '}, + 637: {LaTeXMode.textmode: '\\Elzrtlr '}, + 638: {LaTeXMode.textmode: '\\Elzfhr '}, + 642: {LaTeXMode.textmode: '\\Elzrtls '}, + 643: {LaTeXMode.textmode: '\\Elzesh '}, + 647: {LaTeXMode.textmode: '\\Elztrnt '}, + 648: {LaTeXMode.textmode: '\\Elzrtlt '}, + 650: {LaTeXMode.textmode: '\\Elzpupsil '}, + 651: {LaTeXMode.textmode: '\\Elzpscrv '}, + 652: {LaTeXMode.textmode: '\\Elzinvv '}, + 653: {LaTeXMode.textmode: '\\Elzinvw '}, + 654: {LaTeXMode.textmode: '\\Elztrny '}, + 656: {LaTeXMode.textmode: '\\Elzrtlz '}, + 658: {LaTeXMode.textmode: '\\Elzyogh '}, + 660: {LaTeXMode.textmode: '\\Elzglst '}, + 661: {LaTeXMode.textmode: '\\Elzreglst '}, + 662: {LaTeXMode.textmode: '\\Elzinglst '}, + 670: {LaTeXMode.textmode: '\\textturnk '}, + 676: {LaTeXMode.textmode: '\\Elzdyogh '}, + 679: {LaTeXMode.textmode: '\\Elztesh '}, + 700: {LaTeXMode.textmode: "'", LaTeXMode.mathmode: '\\rasp'}, + 711: {LaTeXMode.textmode: '\\textasciicaron '}, + 712: {LaTeXMode.textmode: '\\Elzverts '}, + 716: {LaTeXMode.textmode: '\\Elzverti '}, + 720: {LaTeXMode.textmode: '\\Elzlmrk '}, + 721: {LaTeXMode.textmode: '\\Elzhlmrk '}, + 722: {LaTeXMode.textmode: '\\Elzsbrhr '}, + 723: {LaTeXMode.textmode: '\\Elzsblhr '}, + 724: {LaTeXMode.textmode: '\\Elzrais '}, + 725: {LaTeXMode.textmode: '\\Elzlow '}, + 728: {LaTeXMode.textmode: '\\textasciibreve ', LaTeXMode.mathmode: '\\u'}, + 729: {LaTeXMode.textmode: '\\textperiodcentered ', LaTeXMode.mathmode: '\\dot{}'}, + 730: {LaTeXMode.textmode: '\\r{}', LaTeXMode.mathmode: '\\mathring{}'}, + 731: {LaTeXMode.textmode: '\\k{}', LaTeXMode.mathmode: '\\mbox{\\k{}}'}, + 732: {LaTeXMode.textmode: '\\texttildelow '}, + 733: {LaTeXMode.textmode: '\\H{}', LaTeXMode.mathmode: '\\mbox{\\H{}}'}, + 741: {LaTeXMode.textmode: '\\tone{55}'}, + 742: {LaTeXMode.textmode: '\\tone{44}'}, + 743: {LaTeXMode.textmode: '\\tone{33}'}, + 744: {LaTeXMode.textmode: '\\tone{22}'}, + 745: {LaTeXMode.textmode: '\\tone{11}'}, + 768: {LaTeXMode.textmode: '\\`', LaTeXMode.mathmode: '\\grave'}, + 769: {LaTeXMode.textmode: "\\'", LaTeXMode.mathmode: '\\acute'}, + 770: {LaTeXMode.textmode: '\\^', LaTeXMode.mathmode: '\\hat'}, + 771: {LaTeXMode.textmode: '\\~', LaTeXMode.mathmode: '\\tilde'}, + 772: {LaTeXMode.textmode: '\\=', LaTeXMode.mathmode: '\\bar'}, + 774: {LaTeXMode.textmode: '\\u', LaTeXMode.mathmode: '\\breve'}, + 775: {LaTeXMode.textmode: '\\.', LaTeXMode.mathmode: '\\dot'}, + 776: {LaTeXMode.textmode: '\\"', LaTeXMode.mathmode: '\\ddot'}, + 778: {LaTeXMode.textmode: '\\r', LaTeXMode.mathmode: '\\ocirc'}, + 779: {LaTeXMode.textmode: '\\H', LaTeXMode.mathmode: '\\H'}, + 780: {LaTeXMode.textmode: '\\v', LaTeXMode.mathmode: '\\check'}, + 783: {LaTeXMode.textmode: '\\cyrchar\\C'}, + 801: {LaTeXMode.textmode: '\\Elzpalh '}, + 807: {LaTeXMode.textmode: '\\c', LaTeXMode.mathmode: '\\c'}, + 810: {LaTeXMode.textmode: '\\Elzsbbrg '}, + 824: {LaTeXMode.mathmode: '\\forks'}, + 902: {LaTeXMode.textmode: "\\'{A}", LaTeXMode.mathmode: '\\acute{A}'}, + 904: {LaTeXMode.textmode: "\\'{E}", LaTeXMode.mathmode: '\\acute{E}'}, + 905: {LaTeXMode.textmode: "\\'{H}", LaTeXMode.mathmode: '\\grave{H}'}, + 906: {LaTeXMode.textmode: "\\'{}{I}", LaTeXMode.mathmode: "\\mathrm{'I}"}, + 908: {LaTeXMode.textmode: "\\'{}O", LaTeXMode.mathmode: "\\mathrm{'O}"}, + 910: {LaTeXMode.textmode: "\\mathrm{'Y}"}, + 911: {LaTeXMode.textmode: "\\mathrm{'\\Omega}"}, + 912: {LaTeXMode.textmode: '\\acute{\\ddot{\\iota}}'}, + 913: {LaTeXMode.textmode: '\\Alpha '}, + 914: {LaTeXMode.textmode: '\\Beta '}, + 915: {LaTeXMode.textmode: '\\Gamma ', LaTeXMode.mathmode: '\\Gamma'}, + 916: {LaTeXMode.textmode: '\\Delta ', LaTeXMode.mathmode: '\\Delta'}, + 917: {LaTeXMode.textmode: '\\Epsilon '}, + 918: {LaTeXMode.textmode: '\\Zeta '}, + 919: {LaTeXMode.textmode: '\\Eta '}, + 920: {LaTeXMode.textmode: '\\Theta ', LaTeXMode.mathmode: '\\Theta'}, + 921: {LaTeXMode.textmode: '\\Iota '}, + 922: {LaTeXMode.textmode: '\\Kappa '}, + 923: {LaTeXMode.textmode: '\\Lambda ', LaTeXMode.mathmode: '\\Lambda'}, + 924: {LaTeXMode.textmode: 'M'}, + 925: {LaTeXMode.textmode: 'N'}, + 926: {LaTeXMode.textmode: '\\Xi ', LaTeXMode.mathmode: '\\Xi'}, + 927: {LaTeXMode.textmode: 'O'}, + 928: {LaTeXMode.textmode: '\\Pi ', LaTeXMode.mathmode: '\\Pi'}, + 929: {LaTeXMode.textmode: '\\Rho '}, + 931: {LaTeXMode.textmode: '\\Sigma ', LaTeXMode.mathmode: '\\Sigma'}, + 932: {LaTeXMode.textmode: '\\Tau '}, + 933: {LaTeXMode.textmode: '\\Upsilon '}, + 934: {LaTeXMode.textmode: '\\Phi ', LaTeXMode.mathmode: '\\Phi'}, + 935: {LaTeXMode.textmode: '\\Chi '}, + 936: {LaTeXMode.textmode: '\\Psi ', LaTeXMode.mathmode: '\\Psi'}, + 937: {LaTeXMode.textmode: '\\Omega ', LaTeXMode.mathmode: '\\Omega'}, + 938: {LaTeXMode.textmode: '\\mathrm{\\ddot{I}}'}, + 939: {LaTeXMode.textmode: '\\mathrm{\\ddot{Y}}'}, + 940: {LaTeXMode.textmode: "\\'{$\\alpha$}", LaTeXMode.mathmode: '\\acute{\\alpha}'}, + 941: {LaTeXMode.textmode: '\\acute{\\epsilon}'}, + 942: {LaTeXMode.textmode: '\\acute{\\eta}'}, + 943: {LaTeXMode.textmode: '\\acute{\\iota}'}, + 944: {LaTeXMode.textmode: '\\acute{\\ddot{\\upsilon}}'}, + 945: {LaTeXMode.textmode: '\\alpha ', LaTeXMode.mathmode: '\\alpha'}, + 946: {LaTeXMode.textmode: '\\beta ', LaTeXMode.mathmode: '\\beta'}, + 947: {LaTeXMode.textmode: '\\gamma ', LaTeXMode.mathmode: '\\gamma'}, + 948: {LaTeXMode.textmode: '\\delta ', LaTeXMode.mathmode: '\\delta'}, + 949: {LaTeXMode.textmode: '\\epsilon ', LaTeXMode.mathmode: '\\varepsilon'}, + 950: {LaTeXMode.textmode: '\\zeta ', LaTeXMode.mathmode: '\\zeta'}, + 951: {LaTeXMode.textmode: '\\eta ', LaTeXMode.mathmode: '\\eta'}, + 952: {LaTeXMode.textmode: '\\texttheta ', LaTeXMode.mathmode: '\\theta'}, + 953: {LaTeXMode.textmode: '\\iota ', LaTeXMode.mathmode: '\\iota'}, + 954: {LaTeXMode.textmode: '\\kappa ', LaTeXMode.mathmode: '\\kappa'}, + 955: {LaTeXMode.textmode: '\\lambda ', LaTeXMode.mathmode: '\\lambda'}, + 956: {LaTeXMode.textmode: '\\mu ', LaTeXMode.mathmode: '\\mu'}, + 957: {LaTeXMode.textmode: '\\nu ', LaTeXMode.mathmode: '\\nu'}, + 958: {LaTeXMode.textmode: '\\xi ', LaTeXMode.mathmode: '\\xi'}, + 959: {LaTeXMode.textmode: 'o'}, + 960: {LaTeXMode.textmode: '\\pi ', LaTeXMode.mathmode: '\\pi'}, + 961: {LaTeXMode.textmode: '\\rho ', LaTeXMode.mathmode: '\\rho'}, + 962: {LaTeXMode.textmode: '\\varsigma ', LaTeXMode.mathmode: '\\varsigma'}, + 963: {LaTeXMode.textmode: '\\sigma ', LaTeXMode.mathmode: '\\sigma'}, + 964: {LaTeXMode.textmode: '\\tau ', LaTeXMode.mathmode: '\\tau'}, + 965: {LaTeXMode.textmode: '\\upsilon ', LaTeXMode.mathmode: '\\upsilon'}, + 966: {LaTeXMode.textmode: '\\varphi ', LaTeXMode.mathmode: '\\varphi'}, + 967: {LaTeXMode.textmode: '\\chi ', LaTeXMode.mathmode: '\\chi'}, + 968: {LaTeXMode.textmode: '\\psi ', LaTeXMode.mathmode: '\\psi'}, + 969: {LaTeXMode.textmode: '\\omega ', LaTeXMode.mathmode: '\\omega'}, + 970: {LaTeXMode.textmode: '\\ddot{\\iota}'}, + 971: {LaTeXMode.textmode: '\\ddot{\\upsilon}'}, + 972: {LaTeXMode.textmode: "\\'{o}", LaTeXMode.mathmode: '\\acute{o}'}, + 973: {LaTeXMode.textmode: '\\acute{\\upsilon}'}, + 974: {LaTeXMode.textmode: '\\acute{\\omega}'}, + 977: {LaTeXMode.textmode: '\\textvartheta ', LaTeXMode.mathmode: '\\vartheta '}, + 978: {LaTeXMode.textmode: '\\Upsilon ', LaTeXMode.mathmode: '\\Upsilon'}, + 981: {LaTeXMode.textmode: '\\phi ', LaTeXMode.mathmode: '\\phi'}, + 982: {LaTeXMode.textmode: '\\varpi ', LaTeXMode.mathmode: '\\varpi'}, + 986: {LaTeXMode.textmode: '\\Stigma '}, + 988: {LaTeXMode.textmode: '\\Digamma '}, + 989: {LaTeXMode.textmode: '\\digamma ', LaTeXMode.mathmode: '\\digamma'}, + 990: {LaTeXMode.textmode: '\\Koppa '}, + 992: {LaTeXMode.textmode: '\\Sampi '}, + 1008: {LaTeXMode.textmode: '\\varkappa ', LaTeXMode.mathmode: '\\varkappa'}, + 1009: {LaTeXMode.textmode: '\\varrho ', LaTeXMode.mathmode: '\\varrho'}, + 1014: {LaTeXMode.textmode: '\\backepsilon ', LaTeXMode.mathmode: '\\backepsilon'}, + 1025: {LaTeXMode.textmode: '\\cyrchar\\CYRYO '}, + 1026: {LaTeXMode.textmode: '\\cyrchar\\CYRDJE '}, + 1027: {LaTeXMode.textmode: "\\cyrchar{\\'\\CYRG}"}, + 1028: {LaTeXMode.textmode: '\\cyrchar\\CYRIE '}, + 1029: {LaTeXMode.textmode: '\\cyrchar\\CYRDZE '}, + 1030: {LaTeXMode.textmode: '\\cyrchar\\CYRII '}, + 1031: {LaTeXMode.textmode: '\\cyrchar\\CYRYI '}, + 1032: {LaTeXMode.textmode: '\\cyrchar\\CYRJE '}, + 1033: {LaTeXMode.textmode: '\\cyrchar\\CYRLJE '}, + 1034: {LaTeXMode.textmode: '\\cyrchar\\CYRNJE '}, + 1035: {LaTeXMode.textmode: '\\cyrchar\\CYRTSHE '}, + 1036: {LaTeXMode.textmode: "\\cyrchar{\\'\\CYRK}"}, + 1038: {LaTeXMode.textmode: '\\cyrchar\\CYRUSHRT '}, + 1039: {LaTeXMode.textmode: '\\cyrchar\\CYRDZHE '}, + 1040: {LaTeXMode.textmode: '\\cyrchar\\CYRA '}, + 1041: {LaTeXMode.textmode: '\\cyrchar\\CYRB '}, + 1042: {LaTeXMode.textmode: '\\cyrchar\\CYRV '}, + 1043: {LaTeXMode.textmode: '\\cyrchar\\CYRG '}, + 1044: {LaTeXMode.textmode: '\\cyrchar\\CYRD '}, + 1045: {LaTeXMode.textmode: '\\cyrchar\\CYRE '}, + 1046: {LaTeXMode.textmode: '\\cyrchar\\CYRZH '}, + 1047: {LaTeXMode.textmode: '\\cyrchar\\CYRZ '}, + 1048: {LaTeXMode.textmode: '\\cyrchar\\CYRI '}, + 1049: {LaTeXMode.textmode: '\\cyrchar\\CYRISHRT '}, + 1050: {LaTeXMode.textmode: '\\cyrchar\\CYRK '}, + 1051: {LaTeXMode.textmode: '\\cyrchar\\CYRL '}, + 1052: {LaTeXMode.textmode: '\\cyrchar\\CYRM '}, + 1053: {LaTeXMode.textmode: '\\cyrchar\\CYRN '}, + 1054: {LaTeXMode.textmode: '\\cyrchar\\CYRO '}, + 1055: {LaTeXMode.textmode: '\\cyrchar\\CYRP '}, + 1056: {LaTeXMode.textmode: '\\cyrchar\\CYRR '}, + 1057: {LaTeXMode.textmode: '\\cyrchar\\CYRS '}, + 1058: {LaTeXMode.textmode: '\\cyrchar\\CYRT '}, + 1059: {LaTeXMode.textmode: '\\cyrchar\\CYRU '}, + 1060: {LaTeXMode.textmode: '\\cyrchar\\CYRF '}, + 1061: {LaTeXMode.textmode: '\\cyrchar\\CYRH '}, + 1062: {LaTeXMode.textmode: '\\cyrchar\\CYRC '}, + 1063: {LaTeXMode.textmode: '\\cyrchar\\CYRCH '}, + 1064: {LaTeXMode.textmode: '\\cyrchar\\CYRSH '}, + 1065: {LaTeXMode.textmode: '\\cyrchar\\CYRSHCH '}, + 1066: {LaTeXMode.textmode: '\\cyrchar\\CYRHRDSN '}, + 1067: {LaTeXMode.textmode: '\\cyrchar\\CYRERY '}, + 1068: {LaTeXMode.textmode: '\\cyrchar\\CYRSFTSN '}, + 1069: {LaTeXMode.textmode: '\\cyrchar\\CYREREV '}, + 1070: {LaTeXMode.textmode: '\\cyrchar\\CYRYU '}, + 1071: {LaTeXMode.textmode: '\\cyrchar\\CYRYA '}, + 1072: {LaTeXMode.textmode: '\\cyrchar\\cyra '}, + 1073: {LaTeXMode.textmode: '\\cyrchar\\cyrb '}, + 1074: {LaTeXMode.textmode: '\\cyrchar\\cyrv '}, + 1075: {LaTeXMode.textmode: '\\cyrchar\\cyrg '}, + 1076: {LaTeXMode.textmode: '\\cyrchar\\cyrd '}, + 1077: {LaTeXMode.textmode: '\\cyrchar\\cyre '}, + 1078: {LaTeXMode.textmode: '\\cyrchar\\cyrzh '}, + 1079: {LaTeXMode.textmode: '\\cyrchar\\cyrz '}, + 1080: {LaTeXMode.textmode: '\\cyrchar\\cyri '}, + 1081: {LaTeXMode.textmode: '\\cyrchar\\cyrishrt '}, + 1082: {LaTeXMode.textmode: '\\cyrchar\\cyrk '}, + 1083: {LaTeXMode.textmode: '\\cyrchar\\cyrl '}, + 1084: {LaTeXMode.textmode: '\\cyrchar\\cyrm '}, + 1085: {LaTeXMode.textmode: '\\cyrchar\\cyrn '}, + 1086: {LaTeXMode.textmode: '\\cyrchar\\cyro '}, + 1087: {LaTeXMode.textmode: '\\cyrchar\\cyrp '}, + 1088: {LaTeXMode.textmode: '\\cyrchar\\cyrr '}, + 1089: {LaTeXMode.textmode: '\\cyrchar\\cyrs '}, + 1090: {LaTeXMode.textmode: '\\cyrchar\\cyrt '}, + 1091: {LaTeXMode.textmode: '\\cyrchar\\cyru '}, + 1092: {LaTeXMode.textmode: '\\cyrchar\\cyrf '}, + 1093: {LaTeXMode.textmode: '\\cyrchar\\cyrh '}, + 1094: {LaTeXMode.textmode: '\\cyrchar\\cyrc '}, + 1095: {LaTeXMode.textmode: '\\cyrchar\\cyrch '}, + 1096: {LaTeXMode.textmode: '\\cyrchar\\cyrsh '}, + 1097: {LaTeXMode.textmode: '\\cyrchar\\cyrshch '}, + 1098: {LaTeXMode.textmode: '\\cyrchar\\cyrhrdsn '}, + 1099: {LaTeXMode.textmode: '\\cyrchar\\cyrery '}, + 1100: {LaTeXMode.textmode: '\\cyrchar\\cyrsftsn '}, + 1101: {LaTeXMode.textmode: '\\cyrchar\\cyrerev '}, + 1102: {LaTeXMode.textmode: '\\cyrchar\\cyryu '}, + 1103: {LaTeXMode.textmode: '\\cyrchar\\cyrya '}, + 1105: {LaTeXMode.textmode: '\\cyrchar\\cyryo '}, + 1106: {LaTeXMode.textmode: '\\cyrchar\\cyrdje '}, + 1107: {LaTeXMode.textmode: "\\cyrchar{\\'\\cyrg}"}, + 1108: {LaTeXMode.textmode: '\\cyrchar\\cyrie '}, + 1109: {LaTeXMode.textmode: '\\cyrchar\\cyrdze '}, + 1110: {LaTeXMode.textmode: '\\cyrchar\\cyrii '}, + 1111: {LaTeXMode.textmode: '\\cyrchar\\cyryi '}, + 1112: {LaTeXMode.textmode: '\\cyrchar\\cyrje '}, + 1113: {LaTeXMode.textmode: '\\cyrchar\\cyrlje '}, + 1114: {LaTeXMode.textmode: '\\cyrchar\\cyrnje '}, + 1115: {LaTeXMode.textmode: '\\cyrchar\\cyrtshe '}, + 1116: {LaTeXMode.textmode: "\\cyrchar{\\'\\cyrk}"}, + 1118: {LaTeXMode.textmode: '\\cyrchar\\cyrushrt '}, + 1119: {LaTeXMode.textmode: '\\cyrchar\\cyrdzhe '}, + 1120: {LaTeXMode.textmode: '\\cyrchar\\CYROMEGA '}, + 1121: {LaTeXMode.textmode: '\\cyrchar\\cyromega '}, + 1122: {LaTeXMode.textmode: '\\cyrchar\\CYRYAT '}, + 1124: {LaTeXMode.textmode: '\\cyrchar\\CYRIOTE '}, + 1125: {LaTeXMode.textmode: '\\cyrchar\\cyriote '}, + 1126: {LaTeXMode.textmode: '\\cyrchar\\CYRLYUS '}, + 1127: {LaTeXMode.textmode: '\\cyrchar\\cyrlyus '}, + 1128: {LaTeXMode.textmode: '\\cyrchar\\CYRIOTLYUS '}, + 1129: {LaTeXMode.textmode: '\\cyrchar\\cyriotlyus '}, + 1130: {LaTeXMode.textmode: '\\cyrchar\\CYRBYUS '}, + 1132: {LaTeXMode.textmode: '\\cyrchar\\CYRIOTBYUS '}, + 1133: {LaTeXMode.textmode: '\\cyrchar\\cyriotbyus '}, + 1134: {LaTeXMode.textmode: '\\cyrchar\\CYRKSI '}, + 1135: {LaTeXMode.textmode: '\\cyrchar\\cyrksi '}, + 1136: {LaTeXMode.textmode: '\\cyrchar\\CYRPSI '}, + 1137: {LaTeXMode.textmode: '\\cyrchar\\cyrpsi '}, + 1138: {LaTeXMode.textmode: '\\cyrchar\\CYRFITA '}, + 1140: {LaTeXMode.textmode: '\\cyrchar\\CYRIZH '}, + 1144: {LaTeXMode.textmode: '\\cyrchar\\CYRUK '}, + 1145: {LaTeXMode.textmode: '\\cyrchar\\cyruk '}, + 1146: {LaTeXMode.textmode: '\\cyrchar\\CYROMEGARND '}, + 1147: {LaTeXMode.textmode: '\\cyrchar\\cyromegarnd '}, + 1148: {LaTeXMode.textmode: '\\cyrchar\\CYROMEGATITLO '}, + 1149: {LaTeXMode.textmode: '\\cyrchar\\cyromegatitlo '}, + 1150: {LaTeXMode.textmode: '\\cyrchar\\CYROT '}, + 1151: {LaTeXMode.textmode: '\\cyrchar\\cyrot '}, + 1152: {LaTeXMode.textmode: '\\cyrchar\\CYRKOPPA '}, + 1153: {LaTeXMode.textmode: '\\cyrchar\\cyrkoppa '}, + 1154: {LaTeXMode.textmode: '\\cyrchar\\cyrthousands '}, + 1160: {LaTeXMode.textmode: '\\cyrchar\\cyrhundredthousands '}, + 1161: {LaTeXMode.textmode: '\\cyrchar\\cyrmillions '}, + 1164: {LaTeXMode.textmode: '\\cyrchar\\CYRSEMISFTSN '}, + 1165: {LaTeXMode.textmode: '\\cyrchar\\cyrsemisftsn '}, + 1166: {LaTeXMode.textmode: '\\cyrchar\\CYRRTICK '}, + 1167: {LaTeXMode.textmode: '\\cyrchar\\cyrrtick '}, + 1168: {LaTeXMode.textmode: '\\cyrchar\\CYRGUP '}, + 1169: {LaTeXMode.textmode: '\\cyrchar\\cyrgup '}, + 1170: {LaTeXMode.textmode: '\\cyrchar\\CYRGHCRS '}, + 1171: {LaTeXMode.textmode: '\\cyrchar\\cyrghcrs '}, + 1172: {LaTeXMode.textmode: '\\cyrchar\\CYRGHK '}, + 1173: {LaTeXMode.textmode: '\\cyrchar\\cyrghk '}, + 1174: {LaTeXMode.textmode: '\\cyrchar\\CYRZHDSC '}, + 1175: {LaTeXMode.textmode: '\\cyrchar\\cyrzhdsc '}, + 1176: {LaTeXMode.textmode: '\\cyrchar\\CYRZDSC '}, + 1177: {LaTeXMode.textmode: '\\cyrchar\\cyrzdsc '}, + 1178: {LaTeXMode.textmode: '\\cyrchar\\CYRKDSC '}, + 1179: {LaTeXMode.textmode: '\\cyrchar\\cyrkdsc '}, + 1180: {LaTeXMode.textmode: '\\cyrchar\\CYRKVCRS '}, + 1181: {LaTeXMode.textmode: '\\cyrchar\\cyrkvcrs '}, + 1182: {LaTeXMode.textmode: '\\cyrchar\\CYRKHCRS '}, + 1183: {LaTeXMode.textmode: '\\cyrchar\\cyrkhcrs '}, + 1184: {LaTeXMode.textmode: '\\cyrchar\\CYRKBEAK '}, + 1185: {LaTeXMode.textmode: '\\cyrchar\\cyrkbeak '}, + 1186: {LaTeXMode.textmode: '\\cyrchar\\CYRNDSC '}, + 1187: {LaTeXMode.textmode: '\\cyrchar\\cyrndsc '}, + 1188: {LaTeXMode.textmode: '\\cyrchar\\CYRNG '}, + 1189: {LaTeXMode.textmode: '\\cyrchar\\cyrng '}, + 1190: {LaTeXMode.textmode: '\\cyrchar\\CYRPHK '}, + 1191: {LaTeXMode.textmode: '\\cyrchar\\cyrphk '}, + 1192: {LaTeXMode.textmode: '\\cyrchar\\CYRABHHA '}, + 1193: {LaTeXMode.textmode: '\\cyrchar\\cyrabhha '}, + 1194: {LaTeXMode.textmode: '\\cyrchar\\CYRSDSC '}, + 1195: {LaTeXMode.textmode: '\\cyrchar\\cyrsdsc '}, + 1196: {LaTeXMode.textmode: '\\cyrchar\\CYRTDSC '}, + 1197: {LaTeXMode.textmode: '\\cyrchar\\cyrtdsc '}, + 1198: {LaTeXMode.textmode: '\\cyrchar\\CYRY '}, + 1199: {LaTeXMode.textmode: '\\cyrchar\\cyry '}, + 1200: {LaTeXMode.textmode: '\\cyrchar\\CYRYHCRS '}, + 1201: {LaTeXMode.textmode: '\\cyrchar\\cyryhcrs '}, + 1202: {LaTeXMode.textmode: '\\cyrchar\\CYRHDSC '}, + 1203: {LaTeXMode.textmode: '\\cyrchar\\cyrhdsc '}, + 1204: {LaTeXMode.textmode: '\\cyrchar\\CYRTETSE '}, + 1205: {LaTeXMode.textmode: '\\cyrchar\\cyrtetse '}, + 1206: {LaTeXMode.textmode: '\\cyrchar\\CYRCHRDSC '}, + 1207: {LaTeXMode.textmode: '\\cyrchar\\cyrchrdsc '}, + 1208: {LaTeXMode.textmode: '\\cyrchar\\CYRCHVCRS '}, + 1209: {LaTeXMode.textmode: '\\cyrchar\\cyrchvcrs '}, + 1210: {LaTeXMode.textmode: '\\cyrchar\\CYRSHHA '}, + 1211: {LaTeXMode.textmode: '\\cyrchar\\cyrshha '}, + 1212: {LaTeXMode.textmode: '\\cyrchar\\CYRABHCH '}, + 1213: {LaTeXMode.textmode: '\\cyrchar\\cyrabhch '}, + 1214: {LaTeXMode.textmode: '\\cyrchar\\CYRABHCHDSC '}, + 1215: {LaTeXMode.textmode: '\\cyrchar\\cyrabhchdsc '}, + 1216: {LaTeXMode.textmode: '\\cyrchar\\CYRpalochka '}, + 1219: {LaTeXMode.textmode: '\\cyrchar\\CYRKHK '}, + 1220: {LaTeXMode.textmode: '\\cyrchar\\cyrkhk '}, + 1223: {LaTeXMode.textmode: '\\cyrchar\\CYRNHK '}, + 1224: {LaTeXMode.textmode: '\\cyrchar\\cyrnhk '}, + 1227: {LaTeXMode.textmode: '\\cyrchar\\CYRCHLDSC '}, + 1228: {LaTeXMode.textmode: '\\cyrchar\\cyrchldsc '}, + 1236: {LaTeXMode.textmode: '\\cyrchar\\CYRAE '}, + 1237: {LaTeXMode.textmode: '\\cyrchar\\cyrae '}, + 1240: {LaTeXMode.textmode: '\\cyrchar\\CYRSCHWA '}, + 1241: {LaTeXMode.textmode: '\\cyrchar\\cyrschwa '}, + 1248: {LaTeXMode.textmode: '\\cyrchar\\CYRABHDZE '}, + 1249: {LaTeXMode.textmode: '\\cyrchar\\cyrabhdze '}, + 1256: {LaTeXMode.textmode: '\\cyrchar\\CYROTLD '}, + 1257: {LaTeXMode.textmode: '\\cyrchar\\cyrotld '}, + 8194: {LaTeXMode.textmode: '\\hspace{0.6em}', LaTeXMode.mathmode: '\\enspace'}, + 8195: {LaTeXMode.textmode: '\\hspace{1em}', LaTeXMode.mathmode: '\\quad'}, + 8196: {LaTeXMode.textmode: '\\hspace{0.33em}'}, + 8197: {LaTeXMode.textmode: '\\hspace{0.25em}', LaTeXMode.mathmode: '\\thickspace'}, + 8198: {LaTeXMode.textmode: '\\hspace{0.166em}'}, + 8199: {LaTeXMode.textmode: '\\hphantom{0}'}, + 8200: {LaTeXMode.textmode: '\\hphantom{,}'}, + 8201: {LaTeXMode.textmode: '\\hspace{0.167em}', LaTeXMode.mathmode: '\\thinspace'}, + 8202: {LaTeXMode.textmode: '\\mkern1mu ', LaTeXMode.mathmode: '\\hspace'}, + 8208: {LaTeXMode.textmode: '-'}, + 8211: {LaTeXMode.textmode: '\\textendash ', LaTeXMode.mathmode: '\\mathrm{\\textendash}'}, + 8212: {LaTeXMode.textmode: '\\textemdash ', LaTeXMode.mathmode: '\\emdash'}, + 8213: {LaTeXMode.textmode: '\\rule{1em}{1pt}'}, + 8214: {LaTeXMode.textmode: '\\Vert ', LaTeXMode.mathmode: '\\Vert'}, + 8216: {LaTeXMode.textmode: '`', LaTeXMode.mathmode: '\\lq'}, + 8217: {LaTeXMode.textmode: "'", LaTeXMode.mathmode: '\\rq'}, + 8218: {LaTeXMode.textmode: ','}, + 8219: {LaTeXMode.textmode: '\\Elzreapos '}, + 8220: {LaTeXMode.textmode: '\\textquotedblleft ', LaTeXMode.mathmode: '\\textquotedblleft'}, + 8221: {LaTeXMode.textmode: '\\textquotedblright ', LaTeXMode.mathmode: '\\textquotedblright'}, + 8222: {LaTeXMode.textmode: ',,'}, + 8224: {LaTeXMode.textmode: '\\textdagger ', LaTeXMode.mathmode: '\\dagger'}, + 8225: {LaTeXMode.textmode: '\\textdaggerdbl ', LaTeXMode.mathmode: '\\ddagger'}, + 8226: {LaTeXMode.textmode: '\\textbullet ', LaTeXMode.mathmode: '\\bullet'}, + 8228: {LaTeXMode.textmode: '.'}, + 8229: {LaTeXMode.textmode: '..'}, + 8230: {LaTeXMode.textmode: '\\ldots ', LaTeXMode.mathmode: '\\dots'}, + 8240: {LaTeXMode.textmode: '\\textperthousand '}, + 8241: {LaTeXMode.textmode: '\\textpertenthousand '}, + 8242: {LaTeXMode.textmode: "{'}", LaTeXMode.mathmode: '\\prime'}, + 8243: {LaTeXMode.textmode: "{''}"}, + 8244: {LaTeXMode.textmode: "{'''}"}, + 8245: {LaTeXMode.textmode: '\\backprime ', LaTeXMode.mathmode: '\\backprime'}, + 8279: {LaTeXMode.textmode: "''''"}, + 8411: {LaTeXMode.textmode: '\\dddot ', LaTeXMode.mathmode: '\\dddot'}, + 8412: {LaTeXMode.textmode: '\\ddddot ', LaTeXMode.mathmode: '\\ddddot'}, + 8421: {LaTeXMode.textmode: '{\\rlap{\\textbackslash}{{/}\\!\\!{/}}}'}, + 8450: {LaTeXMode.textmode: '\\mathbb{C}'}, + 8459: {LaTeXMode.textmode: '\\mathscr{H}'}, + 8460: {LaTeXMode.textmode: '\\mathfrak{H}'}, + 8461: {LaTeXMode.textmode: '\\mathbb{H}'}, + 8463: {LaTeXMode.textmode: '\\hslash ', LaTeXMode.mathmode: '\\hslash'}, + 8464: {LaTeXMode.textmode: '\\mathscr{I}'}, + 8465: {LaTeXMode.textmode: '\\mathfrak{I}', LaTeXMode.mathmode: '\\Im'}, + 8466: {LaTeXMode.textmode: '\\mathscr{L}'}, + 8467: {LaTeXMode.textmode: '\\mathscr{l}', LaTeXMode.mathmode: '\\ell'}, + 8469: {LaTeXMode.textmode: '\\mathbb{N}'}, + 8470: {LaTeXMode.textmode: '\\cyrchar\\textnumero ', LaTeXMode.mathmode: '\\textnumero'}, + 8472: {LaTeXMode.textmode: '\\wp ', LaTeXMode.mathmode: '\\wp'}, + 8473: {LaTeXMode.textmode: '\\mathbb{P}'}, + 8474: {LaTeXMode.textmode: '\\mathbb{Q}'}, + 8475: {LaTeXMode.textmode: '\\mathscr{R}'}, + 8476: {LaTeXMode.textmode: '\\mathfrak{R}', LaTeXMode.mathmode: '\\Re'}, + 8477: {LaTeXMode.textmode: '\\mathbb{R}'}, + 8478: {LaTeXMode.textmode: '\\Elzxrat '}, + 8482: {LaTeXMode.textmode: '\\texttrademark '}, + 8484: {LaTeXMode.textmode: '\\mathbb{Z}'}, + 8486: {LaTeXMode.textmode: '\\Omega '}, + 8487: {LaTeXMode.textmode: '\\mho ', LaTeXMode.mathmode: '\\mho'}, + 8488: {LaTeXMode.textmode: '\\mathfrak{Z}'}, + 8489: {LaTeXMode.textmode: '\\ElsevierGlyph{2129}'}, + 8491: {LaTeXMode.textmode: '\\AA ', LaTeXMode.mathmode: '\\AA'}, + 8492: {LaTeXMode.textmode: '\\mathscr{B}'}, + 8493: {LaTeXMode.textmode: '\\mathfrak{C}'}, + 8495: {LaTeXMode.textmode: '\\mathscr{e}'}, + 8496: {LaTeXMode.textmode: '\\mathscr{E}'}, + 8497: {LaTeXMode.textmode: '\\mathscr{F}'}, + 8499: {LaTeXMode.textmode: '\\mathscr{M}'}, + 8500: {LaTeXMode.textmode: '\\mathscr{o}'}, + 8501: {LaTeXMode.textmode: '\\aleph ', LaTeXMode.mathmode: '\\aleph'}, + 8502: {LaTeXMode.textmode: '\\beth ', LaTeXMode.mathmode: '\\beth'}, + 8503: {LaTeXMode.textmode: '\\gimel ', LaTeXMode.mathmode: '\\gimel'}, + 8504: {LaTeXMode.textmode: '\\daleth ', LaTeXMode.mathmode: '\\daleth'}, + 8512: {LaTeXMode.mathmode: '\\bbsum'}, + 8531: {LaTeXMode.textmode: '\\textfrac{1}{3}'}, + 8532: {LaTeXMode.textmode: '\\textfrac{2}{3}'}, + 8533: {LaTeXMode.textmode: '\\textfrac{1}{5}'}, + 8534: {LaTeXMode.textmode: '\\textfrac{2}{5}'}, + 8535: {LaTeXMode.textmode: '\\textfrac{3}{5}'}, + 8536: {LaTeXMode.textmode: '\\textfrac{4}{5}'}, + 8537: {LaTeXMode.textmode: '\\textfrac{1}{6}'}, + 8538: {LaTeXMode.textmode: '\\textfrac{5}{6}'}, + 8539: {LaTeXMode.textmode: '\\textfrac{1}{8}'}, + 8540: {LaTeXMode.textmode: '\\textfrac{3}{8}'}, + 8541: {LaTeXMode.textmode: '\\textfrac{5}{8}'}, + 8542: {LaTeXMode.textmode: '\\textfrac{7}{8}'}, + 8592: {LaTeXMode.textmode: '\\leftarrow ', LaTeXMode.mathmode: '\\leftarrow'}, + 8593: {LaTeXMode.textmode: '\\uparrow ', LaTeXMode.mathmode: '\\uparrow'}, + 8594: {LaTeXMode.textmode: '\\rightarrow ', LaTeXMode.mathmode: '\\rightarrow'}, + 8595: {LaTeXMode.textmode: '\\downarrow ', LaTeXMode.mathmode: '\\downarrow'}, + 8596: {LaTeXMode.textmode: '\\leftrightarrow ', LaTeXMode.mathmode: '\\leftrightarrow'}, + 8597: {LaTeXMode.textmode: '\\updownarrow ', LaTeXMode.mathmode: '\\updownarrow'}, + 8598: {LaTeXMode.textmode: '\\nwarrow ', LaTeXMode.mathmode: '\\nwarrow'}, + 8599: {LaTeXMode.textmode: '\\nearrow ', LaTeXMode.mathmode: '\\nearrow'}, + 8600: {LaTeXMode.textmode: '\\searrow ', LaTeXMode.mathmode: '\\searrow'}, + 8601: {LaTeXMode.textmode: '\\swarrow ', LaTeXMode.mathmode: '\\swarrow'}, + 8602: {LaTeXMode.textmode: '\\nleftarrow ', LaTeXMode.mathmode: '\\nleftarrow'}, + 8603: {LaTeXMode.textmode: '\\nrightarrow ', LaTeXMode.mathmode: '\\nrightarrow'}, + 8604: {LaTeXMode.textmode: '\\arrowwaveleft ', LaTeXMode.mathmode: '\\leftsquigarrow'}, + 8605: {LaTeXMode.textmode: '\\arrowwaveright ', LaTeXMode.mathmode: '\\rightsquigarrow'}, + 8606: {LaTeXMode.textmode: '\\twoheadleftarrow ', LaTeXMode.mathmode: '\\twoheadleftarrow'}, + 8608: {LaTeXMode.textmode: '\\twoheadrightarrow ', LaTeXMode.mathmode: '\\twoheadrightarrow'}, + 8610: {LaTeXMode.textmode: '\\leftarrowtail ', LaTeXMode.mathmode: '\\leftarrowtail'}, + 8611: {LaTeXMode.textmode: '\\rightarrowtail ', LaTeXMode.mathmode: '\\rightarrowtail'}, + 8614: {LaTeXMode.textmode: '\\mapsto ', LaTeXMode.mathmode: '\\mapsto'}, + 8617: {LaTeXMode.textmode: '\\hookleftarrow ', LaTeXMode.mathmode: '\\hookleftarrow'}, + 8618: {LaTeXMode.textmode: '\\hookrightarrow ', LaTeXMode.mathmode: '\\hookrightarrow'}, + 8619: {LaTeXMode.textmode: '\\looparrowleft ', LaTeXMode.mathmode: '\\looparrowleft'}, + 8620: {LaTeXMode.textmode: '\\looparrowright ', LaTeXMode.mathmode: '\\looparrowright'}, + 8621: {LaTeXMode.textmode: '\\leftrightsquigarrow ', LaTeXMode.mathmode: '\\leftrightsquigarrow'}, + 8622: {LaTeXMode.textmode: '\\nleftrightarrow ', LaTeXMode.mathmode: '\\nleftrightarrow'}, + 8624: {LaTeXMode.textmode: '\\Lsh ', LaTeXMode.mathmode: '\\Lsh'}, + 8625: {LaTeXMode.textmode: '\\Rsh ', LaTeXMode.mathmode: '\\Rsh'}, + 8627: {LaTeXMode.textmode: '\\ElsevierGlyph{21B3}'}, + 8630: {LaTeXMode.textmode: '\\curvearrowleft ', LaTeXMode.mathmode: '\\curvearrowleft'}, + 8631: {LaTeXMode.textmode: '\\curvearrowright ', LaTeXMode.mathmode: '\\curvearrowright'}, + 8634: {LaTeXMode.textmode: '\\circlearrowleft '}, + 8635: {LaTeXMode.textmode: '\\circlearrowright '}, + 8636: {LaTeXMode.textmode: '\\leftharpoonup ', LaTeXMode.mathmode: '\\leftharpoonup'}, + 8637: {LaTeXMode.textmode: '\\leftharpoondown ', LaTeXMode.mathmode: '\\leftharpoondown'}, + 8638: {LaTeXMode.textmode: '\\upharpoonright ', LaTeXMode.mathmode: '\\upharpoonleft'}, + 8639: {LaTeXMode.textmode: '\\upharpoonleft ', LaTeXMode.mathmode: '\\upharpoonright'}, + 8640: {LaTeXMode.textmode: '\\rightharpoonup ', LaTeXMode.mathmode: '\\rightharpoonup'}, + 8641: {LaTeXMode.textmode: '\\rightharpoondown ', LaTeXMode.mathmode: '\\rightharpoondown'}, + 8642: {LaTeXMode.textmode: '\\downharpoonright ', LaTeXMode.mathmode: '\\downharpoonright'}, + 8643: {LaTeXMode.textmode: '\\downharpoonleft ', LaTeXMode.mathmode: '\\downharpoonleft'}, + 8644: {LaTeXMode.textmode: '\\rightleftarrows ', LaTeXMode.mathmode: '\\rightleftarrows'}, + 8645: {LaTeXMode.textmode: '\\dblarrowupdown '}, + 8646: {LaTeXMode.textmode: '\\leftrightarrows ', LaTeXMode.mathmode: '\\leftrightarrows'}, + 8647: {LaTeXMode.textmode: '\\leftleftarrows ', LaTeXMode.mathmode: '\\leftleftarrows'}, + 8648: {LaTeXMode.textmode: '\\upuparrows ', LaTeXMode.mathmode: '\\upuparrows'}, + 8649: {LaTeXMode.textmode: '\\rightrightarrows ', LaTeXMode.mathmode: '\\rightrightarrows'}, + 8650: {LaTeXMode.textmode: '\\downdownarrows ', LaTeXMode.mathmode: '\\downdownarrows'}, + 8651: {LaTeXMode.textmode: '\\leftrightharpoons ', LaTeXMode.mathmode: '\\leftrightharpoons'}, + 8652: {LaTeXMode.textmode: '\\rightleftharpoons ', LaTeXMode.mathmode: '\\rightleftharpoons'}, + 8653: {LaTeXMode.textmode: '\\nLeftarrow ', LaTeXMode.mathmode: '\\nLeftarrow'}, + 8654: {LaTeXMode.textmode: '\\nLeftrightarrow ', LaTeXMode.mathmode: '\\nLeftrightarrow'}, + 8655: {LaTeXMode.textmode: '\\nRightarrow ', LaTeXMode.mathmode: '\\nRightarrow'}, + 8656: {LaTeXMode.textmode: '\\Leftarrow ', LaTeXMode.mathmode: '\\Leftarrow'}, + 8657: {LaTeXMode.textmode: '\\Uparrow ', LaTeXMode.mathmode: '\\Uparrow'}, + 8658: {LaTeXMode.textmode: '\\Rightarrow ', LaTeXMode.mathmode: '\\Rightarrow'}, + 8659: {LaTeXMode.textmode: '\\Downarrow ', LaTeXMode.mathmode: '\\Downarrow'}, + 8660: {LaTeXMode.textmode: '\\Leftrightarrow ', LaTeXMode.mathmode: '\\Leftrightarrow'}, + 8661: {LaTeXMode.textmode: '\\Updownarrow ', LaTeXMode.mathmode: '\\Updownarrow'}, + 8666: {LaTeXMode.textmode: '\\Lleftarrow ', LaTeXMode.mathmode: '\\Lleftarrow'}, + 8667: {LaTeXMode.textmode: '\\Rrightarrow ', LaTeXMode.mathmode: '\\Rrightarrow'}, + 8669: {LaTeXMode.textmode: '\\rightsquigarrow '}, + 8693: {LaTeXMode.textmode: '\\DownArrowUpArrow '}, + 8701: {LaTeXMode.mathmode: '\\leftarrowtriangle'}, + 8702: {LaTeXMode.mathmode: '\\rightarrowtriangle'}, + 8703: {LaTeXMode.mathmode: '\\leftrightarrowtria*'}, + 8704: {LaTeXMode.textmode: '\\forall ', LaTeXMode.mathmode: '\\forall'}, + 8705: {LaTeXMode.textmode: '\\complement ', LaTeXMode.mathmode: '\\complement'}, + 8706: {LaTeXMode.textmode: '\\partial ', LaTeXMode.mathmode: '\\partial'}, + 8707: {LaTeXMode.textmode: '\\exists ', LaTeXMode.mathmode: '\\exists'}, + 8708: {LaTeXMode.textmode: '\\nexists ', LaTeXMode.mathmode: '\\nexists'}, + 8709: {LaTeXMode.textmode: '\\varnothing ', LaTeXMode.mathmode: '\\varnothing'}, + 8711: {LaTeXMode.textmode: '\\nabla ', LaTeXMode.mathmode: '\\nabla'}, + 8712: {LaTeXMode.textmode: '\\in '}, + 8713: {LaTeXMode.textmode: '\\not\\in ', LaTeXMode.mathmode: '\\notin'}, + 8714: {LaTeXMode.mathmode: '\\in'}, + 8715: {LaTeXMode.textmode: '\\ni ', LaTeXMode.mathmode: '\\ni'}, + 8716: {LaTeXMode.textmode: '\\not\\ni '}, + 8719: {LaTeXMode.textmode: '\\prod ', LaTeXMode.mathmode: '\\prod'}, + 8720: {LaTeXMode.textmode: '\\coprod ', LaTeXMode.mathmode: '\\coprod'}, + 8721: {LaTeXMode.textmode: '\\sum ', LaTeXMode.mathmode: '\\sum'}, + 8722: {LaTeXMode.textmode: '-', LaTeXMode.mathmode: '-'}, + 8723: {LaTeXMode.textmode: '\\mp ', LaTeXMode.mathmode: '\\mp'}, + 8724: {LaTeXMode.textmode: '\\dotplus ', LaTeXMode.mathmode: '\\dotplus'}, + 8726: {LaTeXMode.textmode: '\\setminus ', LaTeXMode.mathmode: '\\setminus'}, + 8727: {LaTeXMode.textmode: '{_\\ast}', LaTeXMode.mathmode: '\\ast'}, + 8728: {LaTeXMode.textmode: '\\circ ', LaTeXMode.mathmode: '\\circ'}, + 8729: {LaTeXMode.textmode: '\\bullet '}, + 8730: {LaTeXMode.textmode: '\\surd ', LaTeXMode.mathmode: '\\surd'}, + 8733: {LaTeXMode.textmode: '\\propto ', LaTeXMode.mathmode: '\\propto'}, + 8734: {LaTeXMode.textmode: '\\infty ', LaTeXMode.mathmode: '\\infty'}, + 8735: {LaTeXMode.textmode: '\\rightangle '}, + 8736: {LaTeXMode.textmode: '\\angle ', LaTeXMode.mathmode: '\\angle'}, + 8737: {LaTeXMode.textmode: '\\measuredangle ', LaTeXMode.mathmode: '\\measuredangle'}, + 8738: {LaTeXMode.textmode: '\\sphericalangle ', LaTeXMode.mathmode: '\\sphericalangle'}, + 8739: {LaTeXMode.textmode: '\\mid ', LaTeXMode.mathmode: '\\mid'}, + 8740: {LaTeXMode.textmode: '\\nmid ', LaTeXMode.mathmode: '\\nmid'}, + 8741: {LaTeXMode.textmode: '\\parallel ', LaTeXMode.mathmode: '\\parallel'}, + 8742: {LaTeXMode.textmode: '\\nparallel ', LaTeXMode.mathmode: '\\nparallel'}, + 8743: {LaTeXMode.textmode: '\\wedge ', LaTeXMode.mathmode: '\\wedge'}, + 8744: {LaTeXMode.textmode: '\\vee ', LaTeXMode.mathmode: '\\vee'}, + 8745: {LaTeXMode.textmode: '\\cap ', LaTeXMode.mathmode: '\\cap'}, + 8746: {LaTeXMode.textmode: '\\cup ', LaTeXMode.mathmode: '\\cup'}, + 8747: {LaTeXMode.textmode: '\\int ', LaTeXMode.mathmode: '\\int'}, + 8748: {LaTeXMode.textmode: '\\int\\!\\int ', LaTeXMode.mathmode: '\\iint'}, + 8749: {LaTeXMode.textmode: '\\int\\!\\int\\!\\int ', LaTeXMode.mathmode: '\\iiint'}, + 8750: {LaTeXMode.textmode: '\\oint ', LaTeXMode.mathmode: '\\oint'}, + 8751: {LaTeXMode.textmode: '\\surfintegral ', LaTeXMode.mathmode: '\\oiint'}, + 8752: {LaTeXMode.textmode: '\\volintegral ', LaTeXMode.mathmode: '\\oiiint'}, + 8753: {LaTeXMode.textmode: '\\clwintegral '}, + 8754: {LaTeXMode.textmode: '\\ElsevierGlyph{2232}'}, + 8755: {LaTeXMode.textmode: '\\ElsevierGlyph{2233}'}, + 8756: {LaTeXMode.textmode: '\\therefore ', LaTeXMode.mathmode: '\\therefore'}, + 8757: {LaTeXMode.textmode: '\\because ', LaTeXMode.mathmode: '\\because'}, + 8759: {LaTeXMode.textmode: '\\Colon ', LaTeXMode.mathmode: '\\Colon'}, + 8760: {LaTeXMode.textmode: '\\ElsevierGlyph{2238}', LaTeXMode.mathmode: '\\dotminus'}, + 8762: {LaTeXMode.textmode: '\\mathbin{{:}\\!\\!{-}\\!\\!{:}}'}, + 8763: {LaTeXMode.textmode: '\\homothetic ', LaTeXMode.mathmode: '\\kernelcontraction'}, + 8764: {LaTeXMode.textmode: '\\sim ', LaTeXMode.mathmode: '\\sim'}, + 8765: {LaTeXMode.textmode: '\\backsim ', LaTeXMode.mathmode: '\\backsim'}, + 8766: {LaTeXMode.textmode: '\\lazysinv '}, + 8768: {LaTeXMode.textmode: '\\wr ', LaTeXMode.mathmode: '\\wr'}, + 8769: {LaTeXMode.textmode: '\\not\\sim ', LaTeXMode.mathmode: '\\nsim'}, + 8770: {LaTeXMode.textmode: '\\NotEqualTilde ', LaTeXMode.mathmode: '\\neqsim'}, + 8771: {LaTeXMode.textmode: '\\simeq ', LaTeXMode.mathmode: '\\simeq'}, + 8772: {LaTeXMode.textmode: '\\not\\simeq ', LaTeXMode.mathmode: '\\nsime'}, + 8773: {LaTeXMode.textmode: '\\cong ', LaTeXMode.mathmode: '\\cong'}, + 8774: {LaTeXMode.textmode: '\\approxnotequal '}, + 8775: {LaTeXMode.textmode: '\\not\\cong ', LaTeXMode.mathmode: '\\ncong'}, + 8776: {LaTeXMode.textmode: '\\approx ', LaTeXMode.mathmode: '\\approx'}, + 8777: {LaTeXMode.textmode: '\\not\\approx ', LaTeXMode.mathmode: '\\napprox'}, + 8778: {LaTeXMode.textmode: '\\approxeq ', LaTeXMode.mathmode: '\\approxeq'}, + 8779: {LaTeXMode.textmode: '\\not\\apid '}, + 8780: {LaTeXMode.textmode: '\\allequal '}, + 8781: {LaTeXMode.textmode: '\\asymp ', LaTeXMode.mathmode: '\\asymp'}, + 8782: {LaTeXMode.textmode: '\\NotHumpDownHump ', LaTeXMode.mathmode: '\\nBumpeq'}, + 8783: {LaTeXMode.textmode: '\\NotHumpEqual ', LaTeXMode.mathmode: '\\nbumpeq'}, + 8784: {LaTeXMode.textmode: '\\not\\doteq'}, + 8785: {LaTeXMode.textmode: '\\doteqdot ', LaTeXMode.mathmode: '\\Doteq'}, + 8786: {LaTeXMode.textmode: '\\fallingdotseq ', LaTeXMode.mathmode: '\\fallingdotseq'}, + 8787: {LaTeXMode.textmode: '\\risingdotseq ', LaTeXMode.mathmode: '\\risingdotseq'}, + 8788: {LaTeXMode.textmode: ':=', LaTeXMode.mathmode: '\\coloneq'}, + 8789: {LaTeXMode.textmode: '=:', LaTeXMode.mathmode: '\\eqcolon'}, + 8790: {LaTeXMode.textmode: '\\eqcirc ', LaTeXMode.mathmode: '\\eqcirc'}, + 8791: {LaTeXMode.textmode: '\\circeq ', LaTeXMode.mathmode: '\\circeq'}, + 8793: {LaTeXMode.textmode: '\\estimates ', LaTeXMode.mathmode: '\\wedgeq'}, + 8794: {LaTeXMode.textmode: '\\ElsevierGlyph{225A}'}, + 8795: {LaTeXMode.textmode: '\\starequal '}, + 8796: {LaTeXMode.textmode: '\\triangleq ', LaTeXMode.mathmode: '\\triangleq'}, + 8799: {LaTeXMode.textmode: '\\ElsevierGlyph{225F}', LaTeXMode.mathmode: '\\questeq'}, + 8800: {LaTeXMode.textmode: '\\not =', LaTeXMode.mathmode: '\\ne'}, + 8801: {LaTeXMode.textmode: '\\equiv ', LaTeXMode.mathmode: '\\equiv'}, + 8802: {LaTeXMode.textmode: '\\not\\equiv ', LaTeXMode.mathmode: '\\nequiv'}, + 8804: {LaTeXMode.textmode: '\\leq ', LaTeXMode.mathmode: '\\le'}, + 8805: {LaTeXMode.textmode: '\\geq ', LaTeXMode.mathmode: '\\ge'}, + 8806: {LaTeXMode.textmode: '\\leqq ', LaTeXMode.mathmode: '\\leqq'}, + 8807: {LaTeXMode.textmode: '\\geqq ', LaTeXMode.mathmode: '\\geqq'}, + 8808: {LaTeXMode.textmode: '\\lvertneqq ', LaTeXMode.mathmode: '\\lvertneqq'}, + 8809: {LaTeXMode.textmode: '\\gvertneqq ', LaTeXMode.mathmode: '\\gvertneqq'}, + 8810: {LaTeXMode.textmode: '\\NotLessLess '}, + 8811: {LaTeXMode.textmode: '\\NotGreaterGreater '}, + 8812: {LaTeXMode.textmode: '\\between ', LaTeXMode.mathmode: '\\between'}, + 8813: {LaTeXMode.textmode: '\\not\\kern-0.3em\\times '}, + 8814: {LaTeXMode.textmode: '\\not<', LaTeXMode.mathmode: '\\nless'}, + 8815: {LaTeXMode.textmode: '\\not>', LaTeXMode.mathmode: '\\ngtr'}, + 8816: {LaTeXMode.textmode: '\\not\\leq ', LaTeXMode.mathmode: '\\nleq'}, + 8817: {LaTeXMode.textmode: '\\not\\geq ', LaTeXMode.mathmode: '\\ngeq'}, + 8818: {LaTeXMode.textmode: '\\lessequivlnt ', LaTeXMode.mathmode: '\\lesssim'}, + 8819: {LaTeXMode.textmode: '\\greaterequivlnt ', LaTeXMode.mathmode: '\\gtrsim'}, + 8820: {LaTeXMode.textmode: '\\ElsevierGlyph{2274}'}, + 8821: {LaTeXMode.textmode: '\\ElsevierGlyph{2275}'}, + 8822: {LaTeXMode.textmode: '\\lessgtr ', LaTeXMode.mathmode: '\\lessgtr'}, + 8823: {LaTeXMode.textmode: '\\gtrless ', LaTeXMode.mathmode: '\\gtrless'}, + 8824: {LaTeXMode.textmode: '\\notlessgreater '}, + 8825: {LaTeXMode.textmode: '\\notgreaterless '}, + 8826: {LaTeXMode.textmode: '\\prec ', LaTeXMode.mathmode: '\\prec'}, + 8827: {LaTeXMode.textmode: '\\succ ', LaTeXMode.mathmode: '\\succ'}, + 8828: {LaTeXMode.textmode: '\\preccurlyeq ', LaTeXMode.mathmode: '\\preccurlyeq'}, + 8829: {LaTeXMode.textmode: '\\succcurlyeq ', LaTeXMode.mathmode: '\\succcurlyeq'}, + 8830: {LaTeXMode.textmode: '\\NotPrecedesTilde ', LaTeXMode.mathmode: '\\nprecsim'}, + 8831: {LaTeXMode.textmode: '\\NotSucceedsTilde ', LaTeXMode.mathmode: '\\nsuccsim'}, + 8832: {LaTeXMode.textmode: '\\not\\prec ', LaTeXMode.mathmode: '\\nprec'}, + 8833: {LaTeXMode.textmode: '\\not\\succ ', LaTeXMode.mathmode: '\\nsucc'}, + 8834: {LaTeXMode.textmode: '\\subset ', LaTeXMode.mathmode: '\\subset'}, + 8835: {LaTeXMode.textmode: '\\supset ', LaTeXMode.mathmode: '\\supset'}, + 8836: {LaTeXMode.textmode: '\\not\\subset ', LaTeXMode.mathmode: '\\nsubset'}, + 8837: {LaTeXMode.textmode: '\\not\\supset ', LaTeXMode.mathmode: '\\nsupset'}, + 8838: {LaTeXMode.textmode: '\\subseteq ', LaTeXMode.mathmode: '\\subseteq'}, + 8839: {LaTeXMode.textmode: '\\supseteq ', LaTeXMode.mathmode: '\\supseteq'}, + 8840: {LaTeXMode.textmode: '\\not\\subseteq ', LaTeXMode.mathmode: '\\nsubseteq'}, + 8841: {LaTeXMode.textmode: '\\not\\supseteq ', LaTeXMode.mathmode: '\\nsupseteq'}, + 8842: {LaTeXMode.textmode: '\\varsubsetneqq ', LaTeXMode.mathmode: '\\varsubsetneqq'}, + 8843: {LaTeXMode.textmode: '\\varsupsetneq ', LaTeXMode.mathmode: '\\varsupsetneq'}, + 8846: {LaTeXMode.textmode: '\\uplus ', LaTeXMode.mathmode: '\\uplus'}, + 8847: {LaTeXMode.textmode: '\\NotSquareSubset '}, + 8848: {LaTeXMode.textmode: '\\NotSquareSuperset '}, + 8849: {LaTeXMode.textmode: '\\sqsubseteq ', LaTeXMode.mathmode: '\\sqsubseteq'}, + 8850: {LaTeXMode.textmode: '\\sqsupseteq ', LaTeXMode.mathmode: '\\sqsupseteq'}, + 8851: {LaTeXMode.textmode: '\\sqcap ', LaTeXMode.mathmode: '\\sqcap'}, + 8852: {LaTeXMode.textmode: '\\sqcup ', LaTeXMode.mathmode: '\\sqcup'}, + 8853: {LaTeXMode.textmode: '\\oplus ', LaTeXMode.mathmode: '\\oplus'}, + 8854: {LaTeXMode.textmode: '\\ominus ', LaTeXMode.mathmode: '\\ominus'}, + 8855: {LaTeXMode.textmode: '\\otimes ', LaTeXMode.mathmode: '\\otimes'}, + 8856: {LaTeXMode.textmode: '\\oslash ', LaTeXMode.mathmode: '\\oslash'}, + 8857: {LaTeXMode.textmode: '\\odot ', LaTeXMode.mathmode: '\\odot'}, + 8858: {LaTeXMode.textmode: '\\circledcirc ', LaTeXMode.mathmode: '\\circledcirc'}, + 8859: {LaTeXMode.textmode: '\\circledast ', LaTeXMode.mathmode: '\\circledast'}, + 8861: {LaTeXMode.textmode: '\\circleddash ', LaTeXMode.mathmode: '\\circleddash'}, + 8862: {LaTeXMode.textmode: '\\boxplus ', LaTeXMode.mathmode: '\\boxplus'}, + 8863: {LaTeXMode.textmode: '\\boxminus ', LaTeXMode.mathmode: '\\boxminus'}, + 8864: {LaTeXMode.textmode: '\\boxtimes ', LaTeXMode.mathmode: '\\boxtimes'}, + 8865: {LaTeXMode.textmode: '\\boxdot ', LaTeXMode.mathmode: '\\boxdot'}, + 8866: {LaTeXMode.textmode: '\\vdash ', LaTeXMode.mathmode: '\\vdash'}, + 8867: {LaTeXMode.textmode: '\\dashv ', LaTeXMode.mathmode: '\\dashv'}, + 8868: {LaTeXMode.textmode: '\\top ', LaTeXMode.mathmode: '\\top'}, + 8869: {LaTeXMode.textmode: '\\perp ', LaTeXMode.mathmode: '\\perp'}, + 8871: {LaTeXMode.textmode: '\\truestate ', LaTeXMode.mathmode: '\\models'}, + 8872: {LaTeXMode.textmode: '\\forcesextra ', LaTeXMode.mathmode: '\\vDash'}, + 8873: {LaTeXMode.textmode: '\\Vdash ', LaTeXMode.mathmode: '\\Vdash'}, + 8874: {LaTeXMode.textmode: '\\Vvdash ', LaTeXMode.mathmode: '\\Vvdash'}, + 8875: {LaTeXMode.textmode: '\\VDash '}, + 8876: {LaTeXMode.textmode: '\\nvdash ', LaTeXMode.mathmode: '\\nvdash'}, + 8877: {LaTeXMode.textmode: '\\nvDash ', LaTeXMode.mathmode: '\\nvDash'}, + 8878: {LaTeXMode.textmode: '\\nVdash ', LaTeXMode.mathmode: '\\nVdash'}, + 8879: {LaTeXMode.textmode: '\\nVDash ', LaTeXMode.mathmode: '\\nVDash'}, + 8882: {LaTeXMode.textmode: '\\vartriangleleft ', LaTeXMode.mathmode: '\\vartriangleleft'}, + 8883: {LaTeXMode.textmode: '\\vartriangleright ', LaTeXMode.mathmode: '\\vartriangleright'}, + 8884: {LaTeXMode.textmode: '\\trianglelefteq ', LaTeXMode.mathmode: '\\trianglelefteq'}, + 8885: {LaTeXMode.textmode: '\\trianglerighteq ', LaTeXMode.mathmode: '\\trianglerighteq'}, + 8886: {LaTeXMode.textmode: '\\original '}, + 8887: {LaTeXMode.textmode: '\\image '}, + 8888: {LaTeXMode.textmode: '\\multimap ', LaTeXMode.mathmode: '\\multimap'}, + 8889: {LaTeXMode.textmode: '\\hermitconjmatrix '}, + 8890: {LaTeXMode.textmode: '\\intercal ', LaTeXMode.mathmode: '\\intercal'}, + 8891: {LaTeXMode.textmode: '\\veebar ', LaTeXMode.mathmode: '\\veebar'}, + 8894: {LaTeXMode.textmode: '\\rightanglearc '}, + 8896: {LaTeXMode.textmode: '\\ElsevierGlyph{22C0}', LaTeXMode.mathmode: '\\bigwedge'}, + 8897: {LaTeXMode.textmode: '\\ElsevierGlyph{22C1}', LaTeXMode.mathmode: '\\bigvee'}, + 8898: {LaTeXMode.textmode: '\\bigcap ', LaTeXMode.mathmode: '\\bigcap'}, + 8899: {LaTeXMode.textmode: '\\bigcup ', LaTeXMode.mathmode: '\\bigcup'}, + 8900: {LaTeXMode.textmode: '\\diamond ', LaTeXMode.mathmode: '\\diamond'}, + 8901: {LaTeXMode.textmode: '\\cdot ', LaTeXMode.mathmode: '\\cdot'}, + 8902: {LaTeXMode.textmode: '\\star ', LaTeXMode.mathmode: '\\star'}, + 8903: {LaTeXMode.textmode: '\\divideontimes ', LaTeXMode.mathmode: '\\divideontimes'}, + 8904: {LaTeXMode.textmode: '\\bowtie ', LaTeXMode.mathmode: '\\bowtie'}, + 8905: {LaTeXMode.textmode: '\\ltimes ', LaTeXMode.mathmode: '\\ltimes'}, + 8906: {LaTeXMode.textmode: '\\rtimes ', LaTeXMode.mathmode: '\\rtimes'}, + 8907: {LaTeXMode.textmode: '\\leftthreetimes ', LaTeXMode.mathmode: '\\leftthreetimes'}, + 8908: {LaTeXMode.textmode: '\\rightthreetimes ', LaTeXMode.mathmode: '\\rightthreetimes'}, + 8909: {LaTeXMode.textmode: '\\backsimeq ', LaTeXMode.mathmode: '\\backsimeq'}, + 8910: {LaTeXMode.textmode: '\\curlyvee ', LaTeXMode.mathmode: '\\curlyvee'}, + 8911: {LaTeXMode.textmode: '\\curlywedge ', LaTeXMode.mathmode: '\\curlywedge'}, + 8912: {LaTeXMode.textmode: '\\Subset ', LaTeXMode.mathmode: '\\Subset'}, + 8913: {LaTeXMode.textmode: '\\Supset ', LaTeXMode.mathmode: '\\Supset'}, + 8914: {LaTeXMode.textmode: '\\Cap ', LaTeXMode.mathmode: '\\Cap'}, + 8915: {LaTeXMode.textmode: '\\Cup ', LaTeXMode.mathmode: '\\Cup'}, + 8916: {LaTeXMode.textmode: '\\pitchfork ', LaTeXMode.mathmode: '\\pitchfork'}, + 8918: {LaTeXMode.textmode: '\\lessdot ', LaTeXMode.mathmode: '\\lessdot'}, + 8919: {LaTeXMode.textmode: '\\gtrdot ', LaTeXMode.mathmode: '\\gtrdot'}, + 8920: {LaTeXMode.textmode: '\\verymuchless '}, + 8921: {LaTeXMode.textmode: '\\verymuchgreater ', LaTeXMode.mathmode: '\\ggg'}, + 8922: {LaTeXMode.textmode: '\\lesseqgtr ', LaTeXMode.mathmode: '\\lesseqgtr'}, + 8923: {LaTeXMode.textmode: '\\gtreqless ', LaTeXMode.mathmode: '\\gtreqless'}, + 8926: {LaTeXMode.textmode: '\\curlyeqprec ', LaTeXMode.mathmode: '\\curlyeqprec'}, + 8927: {LaTeXMode.textmode: '\\curlyeqsucc ', LaTeXMode.mathmode: '\\curlyeqsucc'}, + 8930: {LaTeXMode.textmode: '\\not\\sqsubseteq '}, + 8931: {LaTeXMode.textmode: '\\not\\sqsupseteq '}, + 8933: {LaTeXMode.textmode: '\\Elzsqspne '}, + 8934: {LaTeXMode.textmode: '\\lnsim ', LaTeXMode.mathmode: '\\lnsim'}, + 8935: {LaTeXMode.textmode: '\\gnsim ', LaTeXMode.mathmode: '\\gnsim'}, + 8936: {LaTeXMode.textmode: '\\precedesnotsimilar ', LaTeXMode.mathmode: '\\precnsim'}, + 8937: {LaTeXMode.textmode: '\\succnsim ', LaTeXMode.mathmode: '\\succnsim'}, + 8938: {LaTeXMode.textmode: '\\ntriangleleft ', LaTeXMode.mathmode: '\\ntriangleleft'}, + 8939: {LaTeXMode.textmode: '\\ntriangleright ', LaTeXMode.mathmode: '\\ntriangleright'}, + 8940: {LaTeXMode.textmode: '\\ntrianglelefteq ', LaTeXMode.mathmode: '\\ntrianglelefteq'}, + 8941: {LaTeXMode.textmode: '\\ntrianglerighteq ', LaTeXMode.mathmode: '\\ntrianglerighteq'}, + 8942: {LaTeXMode.textmode: '\\vdots ', LaTeXMode.mathmode: '\\vdots'}, + 8943: {LaTeXMode.textmode: '\\cdots ', LaTeXMode.mathmode: '\\cdots'}, + 8944: {LaTeXMode.textmode: '\\upslopeellipsis ', LaTeXMode.mathmode: '\\adots'}, + 8945: {LaTeXMode.textmode: '\\downslopeellipsis ', LaTeXMode.mathmode: '\\ddots'}, + 8966: {LaTeXMode.textmode: '\\varperspcorrespond '}, + 8968: {LaTeXMode.textmode: '\\lceil ', LaTeXMode.mathmode: '\\lceil'}, + 8969: {LaTeXMode.textmode: '\\rceil ', LaTeXMode.mathmode: '\\rceil'}, + 8970: {LaTeXMode.textmode: '\\lfloor ', LaTeXMode.mathmode: '\\lfloor'}, + 8971: {LaTeXMode.textmode: '\\rfloor ', LaTeXMode.mathmode: '\\rfloor'}, + 8981: {LaTeXMode.textmode: '\\recorder '}, + 8982: {LaTeXMode.textmode: '\\mathchar"2208'}, + 8988: {LaTeXMode.textmode: '\\ulcorner ', LaTeXMode.mathmode: '\\ulcorner'}, + 8989: {LaTeXMode.textmode: '\\urcorner ', LaTeXMode.mathmode: '\\urcorner'}, + 8990: {LaTeXMode.textmode: '\\llcorner ', LaTeXMode.mathmode: '\\llcorner'}, + 8991: {LaTeXMode.textmode: '\\lrcorner ', LaTeXMode.mathmode: '\\lrcorner'}, + 8994: {LaTeXMode.textmode: '\\frown ', LaTeXMode.mathmode: '\\frown'}, + 8995: {LaTeXMode.textmode: '\\smile ', LaTeXMode.mathmode: '\\smile'}, + 9021: {LaTeXMode.textmode: '\\ElsevierGlyph{E838}', LaTeXMode.mathmode: '\\obar'}, + 9123: {LaTeXMode.textmode: '\\Elzdlcorn '}, + 9136: {LaTeXMode.textmode: '\\lmoustache ', LaTeXMode.mathmode: '\\lmoustache'}, + 9137: {LaTeXMode.textmode: '\\rmoustache ', LaTeXMode.mathmode: '\\rmoustache'}, + 9251: {LaTeXMode.textmode: '\\textvisiblespace '}, + 9312: {LaTeXMode.textmode: '\\ding{172}'}, + 9313: {LaTeXMode.textmode: '\\ding{173}'}, + 9314: {LaTeXMode.textmode: '\\ding{174}'}, + 9315: {LaTeXMode.textmode: '\\ding{175}'}, + 9316: {LaTeXMode.textmode: '\\ding{176}'}, + 9317: {LaTeXMode.textmode: '\\ding{177}'}, + 9318: {LaTeXMode.textmode: '\\ding{178}'}, + 9319: {LaTeXMode.textmode: '\\ding{179}'}, + 9320: {LaTeXMode.textmode: '\\ding{180}'}, + 9321: {LaTeXMode.textmode: '\\ding{181}'}, + 9416: {LaTeXMode.textmode: '\\circledS ', LaTeXMode.mathmode: '\\circledS'}, + 9478: {LaTeXMode.textmode: '\\Elzdshfnc '}, + 9497: {LaTeXMode.textmode: '\\Elzsqfnw '}, + 9585: {LaTeXMode.textmode: '\\diagup ', LaTeXMode.mathmode: '\\diagup'}, + 9586: {LaTeXMode.mathmode: '\\diagdown'}, + 9632: {LaTeXMode.textmode: '\\ding{110}', LaTeXMode.mathmode: '\\blacksquare'}, + 9633: {LaTeXMode.textmode: '\\square ', LaTeXMode.mathmode: '\\square'}, + 9642: {LaTeXMode.textmode: '\\blacksquare '}, + 9645: {LaTeXMode.textmode: '\\fbox{~~}'}, + 9647: {LaTeXMode.textmode: '\\Elzvrecto '}, + 9649: {LaTeXMode.textmode: '\\ElsevierGlyph{E381}'}, + 9650: {LaTeXMode.textmode: '\\ding{115}'}, + 9651: {LaTeXMode.textmode: '\\bigtriangleup ', LaTeXMode.mathmode: '\\bigtriangleup'}, + 9652: {LaTeXMode.textmode: '\\blacktriangle ', LaTeXMode.mathmode: '\\blacktriangle'}, + 9653: {LaTeXMode.textmode: '\\vartriangle ', LaTeXMode.mathmode: '\\vartriangle'}, + 9656: {LaTeXMode.textmode: '\\blacktriangleright ', LaTeXMode.mathmode: '\\blacktriangleright'}, + 9657: {LaTeXMode.textmode: '\\triangleright ', LaTeXMode.mathmode: '\\triangleright'}, + 9660: {LaTeXMode.textmode: '\\ding{116}'}, + 9661: {LaTeXMode.textmode: '\\bigtriangledown ', LaTeXMode.mathmode: '\\bigtriangledown'}, + 9662: {LaTeXMode.textmode: '\\blacktriangledown ', LaTeXMode.mathmode: '\\blacktriangledown'}, + 9663: {LaTeXMode.textmode: '\\triangledown ', LaTeXMode.mathmode: '\\triangledown'}, + 9666: {LaTeXMode.textmode: '\\blacktriangleleft ', LaTeXMode.mathmode: '\\blacktriangleleft'}, + 9667: {LaTeXMode.textmode: '\\triangleleft ', LaTeXMode.mathmode: '\\triangleleft'}, + 9670: {LaTeXMode.textmode: '\\ding{117}'}, + 9674: {LaTeXMode.textmode: '\\lozenge ', LaTeXMode.mathmode: '\\lozenge'}, + 9675: {LaTeXMode.textmode: '\\bigcirc ', LaTeXMode.mathmode: '\\bigcirc'}, + 9679: {LaTeXMode.textmode: '\\ding{108}'}, + 9680: {LaTeXMode.textmode: '\\Elzcirfl '}, + 9681: {LaTeXMode.textmode: '\\Elzcirfr '}, + 9682: {LaTeXMode.textmode: '\\Elzcirfb '}, + 9687: {LaTeXMode.textmode: '\\ding{119}'}, + 9688: {LaTeXMode.textmode: '\\Elzrvbull '}, + 9703: {LaTeXMode.textmode: '\\Elzsqfl '}, + 9704: {LaTeXMode.textmode: '\\Elzsqfr '}, + 9706: {LaTeXMode.textmode: '\\Elzsqfse '}, + 9711: {LaTeXMode.textmode: '\\bigcirc '}, + 9733: {LaTeXMode.textmode: '\\ding{72}', LaTeXMode.mathmode: '\\bigstar'}, + 9742: {LaTeXMode.textmode: '\\ding{37}'}, + 9755: {LaTeXMode.textmode: '\\ding{42}'}, + 9758: {LaTeXMode.textmode: '\\ding{43}'}, + 9792: {LaTeXMode.textmode: '\\venus '}, + 9794: {LaTeXMode.textmode: '\\male '}, + 9799: {LaTeXMode.textmode: '\\pluto '}, + 9800: {LaTeXMode.textmode: '\\aries '}, + 9801: {LaTeXMode.textmode: '\\taurus '}, + 9802: {LaTeXMode.textmode: '\\gemini '}, + 9803: {LaTeXMode.textmode: '\\cancer '}, + 9804: {LaTeXMode.textmode: '\\leo '}, + 9805: {LaTeXMode.textmode: '\\virgo '}, + 9806: {LaTeXMode.textmode: '\\libra '}, + 9807: {LaTeXMode.textmode: '\\scorpio '}, + 9808: {LaTeXMode.textmode: '\\sagittarius '}, + 9809: {LaTeXMode.textmode: '\\capricornus '}, + 9810: {LaTeXMode.textmode: '\\aquarius '}, + 9811: {LaTeXMode.textmode: '\\pisces '}, + 9824: {LaTeXMode.textmode: '\\ding{171}', LaTeXMode.mathmode: '\\spadesuit'}, + 9826: {LaTeXMode.textmode: '\\diamond ', LaTeXMode.mathmode: '\\diamondsuit'}, + 9827: {LaTeXMode.textmode: '\\ding{168}', LaTeXMode.mathmode: '\\clubsuit'}, + 9829: {LaTeXMode.textmode: '\\ding{170}'}, + 9830: {LaTeXMode.textmode: '\\ding{169}'}, + 9833: {LaTeXMode.textmode: '\\quarternote '}, + 9834: {LaTeXMode.textmode: '\\eighthnote '}, + 9837: {LaTeXMode.textmode: '\\flat ', LaTeXMode.mathmode: '\\flat'}, + 9838: {LaTeXMode.textmode: '\\natural ', LaTeXMode.mathmode: '\\natural'}, + 9839: {LaTeXMode.textmode: '\\sharp ', LaTeXMode.mathmode: '\\sharp'}, + 9985: {LaTeXMode.textmode: '\\ding{33}'}, + 9986: {LaTeXMode.textmode: '\\ding{34}'}, + 9987: {LaTeXMode.textmode: '\\ding{35}'}, + 9988: {LaTeXMode.textmode: '\\ding{36}'}, + 9990: {LaTeXMode.textmode: '\\ding{38}'}, + 9991: {LaTeXMode.textmode: '\\ding{39}'}, + 9992: {LaTeXMode.textmode: '\\ding{40}'}, + 9993: {LaTeXMode.textmode: '\\ding{41}'}, + 9996: {LaTeXMode.textmode: '\\ding{44}'}, + 9997: {LaTeXMode.textmode: '\\ding{45}'}, + 9998: {LaTeXMode.textmode: '\\ding{46}'}, + 9999: {LaTeXMode.textmode: '\\ding{47}'}, + 10000: {LaTeXMode.textmode: '\\ding{48}'}, + 10001: {LaTeXMode.textmode: '\\ding{49}'}, + 10002: {LaTeXMode.textmode: '\\ding{50}'}, + 10003: {LaTeXMode.textmode: '\\ding{51}', LaTeXMode.mathmode: '\\checkmark'}, + 10004: {LaTeXMode.textmode: '\\ding{52}'}, + 10005: {LaTeXMode.textmode: '\\ding{53}'}, + 10006: {LaTeXMode.textmode: '\\ding{54}'}, + 10007: {LaTeXMode.textmode: '\\ding{55}'}, + 10008: {LaTeXMode.textmode: '\\ding{56}'}, + 10009: {LaTeXMode.textmode: '\\ding{57}'}, + 10010: {LaTeXMode.textmode: '\\ding{58}'}, + 10011: {LaTeXMode.textmode: '\\ding{59}'}, + 10012: {LaTeXMode.textmode: '\\ding{60}'}, + 10013: {LaTeXMode.textmode: '\\ding{61}'}, + 10014: {LaTeXMode.textmode: '\\ding{62}'}, + 10015: {LaTeXMode.textmode: '\\ding{63}'}, + 10016: {LaTeXMode.textmode: '\\ding{64}', LaTeXMode.mathmode: '\\maltese'}, + 10017: {LaTeXMode.textmode: '\\ding{65}'}, + 10018: {LaTeXMode.textmode: '\\ding{66}'}, + 10019: {LaTeXMode.textmode: '\\ding{67}'}, + 10020: {LaTeXMode.textmode: '\\ding{68}'}, + 10021: {LaTeXMode.textmode: '\\ding{69}'}, + 10022: {LaTeXMode.textmode: '\\ding{70}'}, + 10023: {LaTeXMode.textmode: '\\ding{71}'}, + 10025: {LaTeXMode.textmode: '\\ding{73}'}, + 10026: {LaTeXMode.textmode: '\\ding{74}'}, + 10027: {LaTeXMode.textmode: '\\ding{75}'}, + 10028: {LaTeXMode.textmode: '\\ding{76}'}, + 10029: {LaTeXMode.textmode: '\\ding{77}'}, + 10030: {LaTeXMode.textmode: '\\ding{78}'}, + 10031: {LaTeXMode.textmode: '\\ding{79}'}, + 10032: {LaTeXMode.textmode: '\\ding{80}'}, + 10033: {LaTeXMode.textmode: '\\ding{81}'}, + 10034: {LaTeXMode.textmode: '\\ding{82}'}, + 10035: {LaTeXMode.textmode: '\\ding{83}'}, + 10036: {LaTeXMode.textmode: '\\ding{84}'}, + 10037: {LaTeXMode.textmode: '\\ding{85}'}, + 10038: {LaTeXMode.textmode: '\\ding{86}'}, + 10039: {LaTeXMode.textmode: '\\ding{87}'}, + 10040: {LaTeXMode.textmode: '\\ding{88}'}, + 10041: {LaTeXMode.textmode: '\\ding{89}'}, + 10042: {LaTeXMode.textmode: '\\ding{90}'}, + 10043: {LaTeXMode.textmode: '\\ding{91}'}, + 10044: {LaTeXMode.textmode: '\\ding{92}'}, + 10045: {LaTeXMode.textmode: '\\ding{93}'}, + 10046: {LaTeXMode.textmode: '\\ding{94}'}, + 10047: {LaTeXMode.textmode: '\\ding{95}'}, + 10048: {LaTeXMode.textmode: '\\ding{96}'}, + 10049: {LaTeXMode.textmode: '\\ding{97}'}, + 10050: {LaTeXMode.textmode: '\\ding{98}'}, + 10051: {LaTeXMode.textmode: '\\ding{99}'}, + 10052: {LaTeXMode.textmode: '\\ding{100}'}, + 10053: {LaTeXMode.textmode: '\\ding{101}'}, + 10054: {LaTeXMode.textmode: '\\ding{102}'}, + 10055: {LaTeXMode.textmode: '\\ding{103}'}, + 10056: {LaTeXMode.textmode: '\\ding{104}'}, + 10057: {LaTeXMode.textmode: '\\ding{105}'}, + 10058: {LaTeXMode.textmode: '\\ding{106}'}, + 10059: {LaTeXMode.textmode: '\\ding{107}'}, + 10061: {LaTeXMode.textmode: '\\ding{109}'}, + 10063: {LaTeXMode.textmode: '\\ding{111}'}, + 10064: {LaTeXMode.textmode: '\\ding{112}'}, + 10065: {LaTeXMode.textmode: '\\ding{113}'}, + 10066: {LaTeXMode.textmode: '\\ding{114}'}, + 10070: {LaTeXMode.textmode: '\\ding{118}'}, + 10072: {LaTeXMode.textmode: '\\ding{120}'}, + 10073: {LaTeXMode.textmode: '\\ding{121}'}, + 10074: {LaTeXMode.textmode: '\\ding{122}'}, + 10075: {LaTeXMode.textmode: '\\ding{123}'}, + 10076: {LaTeXMode.textmode: '\\ding{124}'}, + 10077: {LaTeXMode.textmode: '\\ding{125}'}, + 10078: {LaTeXMode.textmode: '\\ding{126}'}, + 10081: {LaTeXMode.textmode: '\\ding{161}'}, + 10082: {LaTeXMode.textmode: '\\ding{162}'}, + 10083: {LaTeXMode.textmode: '\\ding{163}'}, + 10084: {LaTeXMode.textmode: '\\ding{164}'}, + 10085: {LaTeXMode.textmode: '\\ding{165}'}, + 10086: {LaTeXMode.textmode: '\\ding{166}'}, + 10087: {LaTeXMode.textmode: '\\ding{167}'}, + 10102: {LaTeXMode.textmode: '\\ding{182}'}, + 10103: {LaTeXMode.textmode: '\\ding{183}'}, + 10104: {LaTeXMode.textmode: '\\ding{184}'}, + 10105: {LaTeXMode.textmode: '\\ding{185}'}, + 10106: {LaTeXMode.textmode: '\\ding{186}'}, + 10107: {LaTeXMode.textmode: '\\ding{187}'}, + 10108: {LaTeXMode.textmode: '\\ding{188}'}, + 10109: {LaTeXMode.textmode: '\\ding{189}'}, + 10110: {LaTeXMode.textmode: '\\ding{190}'}, + 10111: {LaTeXMode.textmode: '\\ding{191}'}, + 10112: {LaTeXMode.textmode: '\\ding{192}'}, + 10113: {LaTeXMode.textmode: '\\ding{193}'}, + 10114: {LaTeXMode.textmode: '\\ding{194}'}, + 10115: {LaTeXMode.textmode: '\\ding{195}'}, + 10116: {LaTeXMode.textmode: '\\ding{196}'}, + 10117: {LaTeXMode.textmode: '\\ding{197}'}, + 10118: {LaTeXMode.textmode: '\\ding{198}'}, + 10119: {LaTeXMode.textmode: '\\ding{199}'}, + 10120: {LaTeXMode.textmode: '\\ding{200}'}, + 10121: {LaTeXMode.textmode: '\\ding{201}'}, + 10122: {LaTeXMode.textmode: '\\ding{202}'}, + 10123: {LaTeXMode.textmode: '\\ding{203}'}, + 10124: {LaTeXMode.textmode: '\\ding{204}'}, + 10125: {LaTeXMode.textmode: '\\ding{205}'}, + 10126: {LaTeXMode.textmode: '\\ding{206}'}, + 10127: {LaTeXMode.textmode: '\\ding{207}'}, + 10128: {LaTeXMode.textmode: '\\ding{208}'}, + 10129: {LaTeXMode.textmode: '\\ding{209}'}, + 10130: {LaTeXMode.textmode: '\\ding{210}'}, + 10131: {LaTeXMode.textmode: '\\ding{211}'}, + 10132: {LaTeXMode.textmode: '\\ding{212}'}, + 10136: {LaTeXMode.textmode: '\\ding{216}'}, + 10137: {LaTeXMode.textmode: '\\ding{217}'}, + 10138: {LaTeXMode.textmode: '\\ding{218}'}, + 10139: {LaTeXMode.textmode: '\\ding{219}'}, + 10140: {LaTeXMode.textmode: '\\ding{220}'}, + 10141: {LaTeXMode.textmode: '\\ding{221}'}, + 10142: {LaTeXMode.textmode: '\\ding{222}'}, + 10143: {LaTeXMode.textmode: '\\ding{223}'}, + 10144: {LaTeXMode.textmode: '\\ding{224}'}, + 10145: {LaTeXMode.textmode: '\\ding{225}'}, + 10146: {LaTeXMode.textmode: '\\ding{226}'}, + 10147: {LaTeXMode.textmode: '\\ding{227}'}, + 10148: {LaTeXMode.textmode: '\\ding{228}'}, + 10149: {LaTeXMode.textmode: '\\ding{229}'}, + 10150: {LaTeXMode.textmode: '\\ding{230}'}, + 10151: {LaTeXMode.textmode: '\\ding{231}'}, + 10152: {LaTeXMode.textmode: '\\ding{232}'}, + 10153: {LaTeXMode.textmode: '\\ding{233}'}, + 10154: {LaTeXMode.textmode: '\\ding{234}'}, + 10155: {LaTeXMode.textmode: '\\ding{235}'}, + 10156: {LaTeXMode.textmode: '\\ding{236}'}, + 10157: {LaTeXMode.textmode: '\\ding{237}'}, + 10158: {LaTeXMode.textmode: '\\ding{238}'}, + 10159: {LaTeXMode.textmode: '\\ding{239}'}, + 10161: {LaTeXMode.textmode: '\\ding{241}'}, + 10162: {LaTeXMode.textmode: '\\ding{242}'}, + 10163: {LaTeXMode.textmode: '\\ding{243}'}, + 10164: {LaTeXMode.textmode: '\\ding{244}'}, + 10165: {LaTeXMode.textmode: '\\ding{245}'}, + 10166: {LaTeXMode.textmode: '\\ding{246}'}, + 10167: {LaTeXMode.textmode: '\\ding{247}'}, + 10168: {LaTeXMode.textmode: '\\ding{248}'}, + 10169: {LaTeXMode.textmode: '\\ding{249}'}, + 10170: {LaTeXMode.textmode: '\\ding{250}'}, + 10171: {LaTeXMode.textmode: '\\ding{251}'}, + 10172: {LaTeXMode.textmode: '\\ding{252}'}, + 10173: {LaTeXMode.textmode: '\\ding{253}'}, + 10174: {LaTeXMode.textmode: '\\ding{254}'}, + 10229: {LaTeXMode.textmode: '\\longleftarrow ', LaTeXMode.mathmode: '\\longleftarrow'}, + 10230: {LaTeXMode.textmode: '\\longrightarrow ', LaTeXMode.mathmode: '\\longrightarrow'}, + 10231: {LaTeXMode.textmode: '\\longleftrightarrow ', LaTeXMode.mathmode: '\\longleftrightarrow'}, + 10232: {LaTeXMode.textmode: '\\Longleftarrow ', LaTeXMode.mathmode: '\\Longleftarrow'}, + 10233: {LaTeXMode.textmode: '\\Longrightarrow ', LaTeXMode.mathmode: '\\Longrightarrow'}, + 10234: {LaTeXMode.textmode: '\\Longleftrightarrow ', LaTeXMode.mathmode: '\\Longleftrightarrow'}, + 10236: {LaTeXMode.textmode: '\\longmapsto ', LaTeXMode.mathmode: '\\longmapsto'}, + 10239: {LaTeXMode.textmode: '\\sim\\joinrel\\leadsto'}, + 10501: {LaTeXMode.textmode: '\\ElsevierGlyph{E212}'}, + 10502: {LaTeXMode.mathmode: '\\Mapsfrom'}, + 10503: {LaTeXMode.mathmode: '\\Mapsto'}, + 10506: {LaTeXMode.mathmode: '\\Uuparrow'}, + 10507: {LaTeXMode.mathmode: '\\Ddownarrow'}, + 10511: {LaTeXMode.mathmode: '\\dbkarow'}, + 10512: {LaTeXMode.mathmode: '\\drbkarrow'}, + 10514: {LaTeXMode.textmode: '\\UpArrowBar '}, + 10515: {LaTeXMode.textmode: '\\DownArrowBar '}, + 10518: {LaTeXMode.mathmode: '\\twoheadrightarrowtail'}, + 10531: {LaTeXMode.textmode: '\\ElsevierGlyph{E20C}'}, + 10532: {LaTeXMode.textmode: '\\ElsevierGlyph{E20D}'}, + 10533: {LaTeXMode.textmode: '\\ElsevierGlyph{E20B}', LaTeXMode.mathmode: '\\hksearow'}, + 10534: {LaTeXMode.textmode: '\\ElsevierGlyph{E20A}', LaTeXMode.mathmode: '\\hkswarow'}, + 10535: {LaTeXMode.textmode: '\\ElsevierGlyph{E211}', LaTeXMode.mathmode: '\\tona'}, + 10536: {LaTeXMode.textmode: '\\ElsevierGlyph{E20E}', LaTeXMode.mathmode: '\\toea'}, + 10537: {LaTeXMode.textmode: '\\ElsevierGlyph{E20F}', LaTeXMode.mathmode: '\\tosa'}, + 10538: {LaTeXMode.textmode: '\\ElsevierGlyph{E210}', LaTeXMode.mathmode: '\\towa'}, + 10539: {LaTeXMode.mathmode: '\\rdiagovfdiag'}, + 10540: {LaTeXMode.mathmode: '\\fdiagovrdiag'}, + 10541: {LaTeXMode.mathmode: '\\seovnearrow'}, + 10542: {LaTeXMode.mathmode: '\\neovsearrow'}, + 10543: {LaTeXMode.mathmode: '\\fdiagovnearrow'}, + 10544: {LaTeXMode.mathmode: '\\rdiagovsearrow'}, + 10545: {LaTeXMode.mathmode: '\\neovnwarrow'}, + 10546: {LaTeXMode.mathmode: '\\nwovnearrow'}, + 10547: {LaTeXMode.textmode: '\\ElsevierGlyph{E21D}'}, + 10550: {LaTeXMode.textmode: '\\ElsevierGlyph{E21A}'}, + 10551: {LaTeXMode.textmode: '\\ElsevierGlyph{E219}'}, + 10560: {LaTeXMode.textmode: '\\Elolarr ', LaTeXMode.mathmode: '\\circlearrowleft'}, + 10561: {LaTeXMode.textmode: '\\Elorarr ', LaTeXMode.mathmode: '\\circlearrowright'}, + 10562: {LaTeXMode.textmode: '\\ElzRlarr '}, + 10564: {LaTeXMode.textmode: '\\ElzrLarr '}, + 10567: {LaTeXMode.textmode: '\\Elzrarrx '}, + 10574: {LaTeXMode.textmode: '\\LeftRightVector '}, + 10575: {LaTeXMode.textmode: '\\RightUpDownVector '}, + 10576: {LaTeXMode.textmode: '\\DownLeftRightVector '}, + 10577: {LaTeXMode.textmode: '\\LeftUpDownVector '}, + 10578: {LaTeXMode.textmode: '\\LeftVectorBar '}, + 10579: {LaTeXMode.textmode: '\\RightVectorBar '}, + 10580: {LaTeXMode.textmode: '\\RightUpVectorBar '}, + 10581: {LaTeXMode.textmode: '\\RightDownVectorBar '}, + 10582: {LaTeXMode.textmode: '\\DownLeftVectorBar '}, + 10583: {LaTeXMode.textmode: '\\DownRightVectorBar '}, + 10584: {LaTeXMode.textmode: '\\LeftUpVectorBar '}, + 10585: {LaTeXMode.textmode: '\\LeftDownVectorBar '}, + 10586: {LaTeXMode.textmode: '\\LeftTeeVector '}, + 10587: {LaTeXMode.textmode: '\\RightTeeVector '}, + 10588: {LaTeXMode.textmode: '\\RightUpTeeVector '}, + 10589: {LaTeXMode.textmode: '\\RightDownTeeVector '}, + 10590: {LaTeXMode.textmode: '\\DownLeftTeeVector '}, + 10591: {LaTeXMode.textmode: '\\DownRightTeeVector '}, + 10592: {LaTeXMode.textmode: '\\LeftUpTeeVector '}, + 10593: {LaTeXMode.textmode: '\\LeftDownTeeVector '}, + 10606: {LaTeXMode.textmode: '\\UpEquilibrium '}, + 10607: {LaTeXMode.textmode: '\\ReverseUpEquilibrium '}, + 10608: {LaTeXMode.textmode: '\\RoundImplies '}, + 10620: {LaTeXMode.textmode: '\\ElsevierGlyph{E214}'}, + 10621: {LaTeXMode.textmode: '\\ElsevierGlyph{E215}'}, + 10624: {LaTeXMode.textmode: '\\Elztfnc ', LaTeXMode.mathmode: '\\Vvert'}, + 10629: {LaTeXMode.textmode: '\\ElsevierGlyph{3018}'}, + 10630: {LaTeXMode.textmode: '\\Elroang '}, + 10643: {LaTeXMode.textmode: '<\\kern-0.58em('}, + 10644: {LaTeXMode.textmode: '\\ElsevierGlyph{E291}'}, + 10649: {LaTeXMode.textmode: '\\Elzddfnc '}, + 10652: {LaTeXMode.textmode: '\\Angle '}, + 10656: {LaTeXMode.textmode: '\\Elzlpargt '}, + 10677: {LaTeXMode.textmode: '\\ElsevierGlyph{E260}'}, + 10678: {LaTeXMode.textmode: '\\ElsevierGlyph{E61B}'}, + 10680: {LaTeXMode.mathmode: '\\obslash'}, + 10692: {LaTeXMode.mathmode: '\\boxdiag'}, + 10693: {LaTeXMode.mathmode: '\\boxbslash'}, + 10694: {LaTeXMode.mathmode: '\\boxast'}, + 10695: {LaTeXMode.mathmode: '\\boxcircle'}, + 10698: {LaTeXMode.textmode: '\\ElzLap '}, + 10699: {LaTeXMode.textmode: '\\Elzdefas '}, + 10703: {LaTeXMode.textmode: '\\NotLeftTriangleBar '}, + 10704: {LaTeXMode.textmode: '\\NotRightTriangleBar '}, + 10716: {LaTeXMode.textmode: '\\ElsevierGlyph{E372}'}, + 10719: {LaTeXMode.mathmode: '\\dualmap'}, + 10722: {LaTeXMode.mathmode: '\\shuffle'}, + 10731: {LaTeXMode.textmode: '\\blacklozenge ', LaTeXMode.mathmode: '\\blacklozenge'}, + 10740: {LaTeXMode.textmode: '\\RuleDelayed '}, + 10752: {LaTeXMode.mathmode: '\\bigodot'}, + 10753: {LaTeXMode.mathmode: '\\bigoplus'}, + 10754: {LaTeXMode.mathmode: '\\bigotimes'}, + 10755: {LaTeXMode.mathmode: '\\bigcupdot'}, + 10756: {LaTeXMode.textmode: '\\Elxuplus ', LaTeXMode.mathmode: '\\biguplus'}, + 10757: {LaTeXMode.textmode: '\\ElzThr ', LaTeXMode.mathmode: '\\bigsqcap'}, + 10758: {LaTeXMode.textmode: '\\Elxsqcup ', LaTeXMode.mathmode: '\\bigsqcup'}, + 10759: {LaTeXMode.textmode: '\\ElzInf ', LaTeXMode.mathmode: '\\conjquant'}, + 10760: {LaTeXMode.textmode: '\\ElzSup ', LaTeXMode.mathmode: '\\disjquant'}, + 10761: {LaTeXMode.mathmode: '\\bigtimes'}, + 10765: {LaTeXMode.textmode: '\\ElzCint ', LaTeXMode.mathmode: '\\intbar'}, + 10766: {LaTeXMode.mathmode: '\\intBar'}, + 10767: {LaTeXMode.textmode: '\\clockoint '}, + 10768: {LaTeXMode.textmode: '\\ElsevierGlyph{E395}'}, + 10774: {LaTeXMode.textmode: '\\sqrint '}, + 10776: {LaTeXMode.mathmode: '\\intx'}, + 10777: {LaTeXMode.mathmode: '\\intcap'}, + 10778: {LaTeXMode.mathmode: '\\intcup'}, + 10779: {LaTeXMode.mathmode: '\\upint'}, + 10780: {LaTeXMode.mathmode: '\\lowint'}, + 10789: {LaTeXMode.textmode: '\\ElsevierGlyph{E25A}', LaTeXMode.mathmode: '\\plusdot'}, + 10794: {LaTeXMode.textmode: '\\ElsevierGlyph{E25B}', LaTeXMode.mathmode: '\\minusdot'}, + 10797: {LaTeXMode.textmode: '\\ElsevierGlyph{E25C}'}, + 10798: {LaTeXMode.textmode: '\\ElsevierGlyph{E25D}'}, + 10799: {LaTeXMode.textmode: '\\ElzTimes '}, + 10802: {LaTeXMode.mathmode: '\\btimes'}, + 10804: {LaTeXMode.textmode: '\\ElsevierGlyph{E25E}'}, + 10805: {LaTeXMode.textmode: '\\ElsevierGlyph{E25E}'}, + 10812: {LaTeXMode.textmode: '\\ElsevierGlyph{E259}', LaTeXMode.mathmode: '\\intprod'}, + 10813: {LaTeXMode.mathmode: '\\intprodr'}, + 10815: {LaTeXMode.textmode: '\\amalg ', LaTeXMode.mathmode: '\\amalg'}, + 10835: {LaTeXMode.textmode: '\\ElzAnd '}, + 10836: {LaTeXMode.textmode: '\\ElzOr '}, + 10837: {LaTeXMode.textmode: '\\ElsevierGlyph{E36E}'}, + 10838: {LaTeXMode.textmode: '\\ElOr '}, + 10846: {LaTeXMode.textmode: '\\perspcorrespond ', LaTeXMode.mathmode: '\\doublebarwedge'}, + 10847: {LaTeXMode.textmode: '\\Elzminhat '}, + 10851: {LaTeXMode.textmode: '\\ElsevierGlyph{225A}'}, + 10862: {LaTeXMode.textmode: '\\stackrel{*}{=}'}, + 10869: {LaTeXMode.textmode: '\\Equal '}, + 10871: {LaTeXMode.mathmode: '\\ddotseq'}, + 10877: {LaTeXMode.textmode: '\\nleqslant ', LaTeXMode.mathmode: '\\nleqslant'}, + 10878: {LaTeXMode.textmode: '\\ngeqslant ', LaTeXMode.mathmode: '\\ngeqslant'}, + 10885: {LaTeXMode.textmode: '\\lessapprox ', LaTeXMode.mathmode: '\\lessapprox'}, + 10886: {LaTeXMode.textmode: '\\gtrapprox ', LaTeXMode.mathmode: '\\gtrapprox'}, + 10887: {LaTeXMode.textmode: '\\lneq ', LaTeXMode.mathmode: '\\lneq'}, + 10888: {LaTeXMode.textmode: '\\gneq ', LaTeXMode.mathmode: '\\gneq'}, + 10889: {LaTeXMode.textmode: '\\lnapprox ', LaTeXMode.mathmode: '\\lnapprox'}, + 10890: {LaTeXMode.textmode: '\\gnapprox ', LaTeXMode.mathmode: '\\gnapprox'}, + 10891: {LaTeXMode.textmode: '\\lesseqqgtr ', LaTeXMode.mathmode: '\\lesseqqgtr'}, + 10892: {LaTeXMode.textmode: '\\gtreqqless ', LaTeXMode.mathmode: '\\gtreqqless'}, + 10901: {LaTeXMode.textmode: '\\eqslantless ', LaTeXMode.mathmode: '\\eqslantless'}, + 10902: {LaTeXMode.textmode: '\\eqslantgtr ', LaTeXMode.mathmode: '\\eqslantgtr'}, + 10909: {LaTeXMode.textmode: '\\Pisymbol{ppi020}{117}'}, + 10910: {LaTeXMode.textmode: '\\Pisymbol{ppi020}{105}'}, + 10913: {LaTeXMode.textmode: '\\NotNestedLessLess '}, + 10914: {LaTeXMode.textmode: '\\NotNestedGreaterGreater '}, + 10915: {LaTeXMode.mathmode: '\\partialmeetcontraction'}, + 10926: {LaTeXMode.mathmode: '\\bumpeqq'}, + 10927: {LaTeXMode.textmode: '\\not\\preceq ', LaTeXMode.mathmode: '\\npreceq'}, + 10928: {LaTeXMode.textmode: '\\not\\succeq ', LaTeXMode.mathmode: '\\nsucceq'}, + 10933: {LaTeXMode.textmode: '\\precneqq ', LaTeXMode.mathmode: '\\precneqq'}, + 10934: {LaTeXMode.textmode: '\\succneqq ', LaTeXMode.mathmode: '\\succneqq'}, + 10935: {LaTeXMode.textmode: '\\precapprox ', LaTeXMode.mathmode: '\\precapprox'}, + 10936: {LaTeXMode.textmode: '\\succapprox ', LaTeXMode.mathmode: '\\succapprox'}, + 10937: {LaTeXMode.textmode: '\\precnapprox ', LaTeXMode.mathmode: '\\precnapprox'}, + 10938: {LaTeXMode.textmode: '\\succnapprox ', LaTeXMode.mathmode: '\\succnapprox'}, + 10949: {LaTeXMode.textmode: '\\nsubseteqq ', LaTeXMode.mathmode: '\\nsubseteqq'}, + 10950: {LaTeXMode.textmode: '\\nsupseteqq'}, + 10955: {LaTeXMode.textmode: '\\subsetneqq ', LaTeXMode.mathmode: '\\subsetneqq'}, + 10956: {LaTeXMode.textmode: '\\supsetneqq ', LaTeXMode.mathmode: '\\supsetneqq'}, + 10971: {LaTeXMode.mathmode: '\\mlcp'}, + 10973: {LaTeXMode.mathmode: '\\forks'}, + 10979: {LaTeXMode.mathmode: '\\dashV'}, + 10980: {LaTeXMode.mathmode: '\\Dashv'}, + 10987: {LaTeXMode.textmode: '\\ElsevierGlyph{E30D}'}, + 10996: {LaTeXMode.mathmode: '\\interleave'}, + 10998: {LaTeXMode.textmode: '\\Elztdcol '}, + 11005: {LaTeXMode.textmode: '{\\rlap{\\textbackslash}{{/}\\!\\!{/}}}'}, + 12298: {LaTeXMode.textmode: '\\ElsevierGlyph{300A}'}, + 12299: {LaTeXMode.textmode: '\\ElsevierGlyph{300B}'}, + 12312: {LaTeXMode.textmode: '\\ElsevierGlyph{3018}'}, + 12313: {LaTeXMode.textmode: '\\ElsevierGlyph{3019}'}, + 12314: {LaTeXMode.textmode: '\\openbracketleft '}, + 12315: {LaTeXMode.textmode: '\\openbracketright '}, + 64256: {LaTeXMode.textmode: 'ff'}, + 64257: {LaTeXMode.textmode: 'fi'}, + 64258: {LaTeXMode.textmode: 'fl'}, + 64259: {LaTeXMode.textmode: 'ffi'}, + 64260: {LaTeXMode.textmode: 'ffl'}, + 65024: {LaTeXMode.textmode: '\\varsupsetneq ', LaTeXMode.mathmode: '\\varsupsetneq'}, + 119808: {LaTeXMode.textmode: '\\mathbf{A}'}, + 119809: {LaTeXMode.textmode: '\\mathbf{B}'}, + 119810: {LaTeXMode.textmode: '\\mathbf{C}'}, + 119811: {LaTeXMode.textmode: '\\mathbf{D}'}, + 119812: {LaTeXMode.textmode: '\\mathbf{E}'}, + 119813: {LaTeXMode.textmode: '\\mathbf{F}'}, + 119814: {LaTeXMode.textmode: '\\mathbf{G}'}, + 119815: {LaTeXMode.textmode: '\\mathbf{H}'}, + 119816: {LaTeXMode.textmode: '\\mathbf{I}'}, + 119817: {LaTeXMode.textmode: '\\mathbf{J}'}, + 119818: {LaTeXMode.textmode: '\\mathbf{K}'}, + 119819: {LaTeXMode.textmode: '\\mathbf{L}'}, + 119820: {LaTeXMode.textmode: '\\mathbf{M}'}, + 119821: {LaTeXMode.textmode: '\\mathbf{N}'}, + 119822: {LaTeXMode.textmode: '\\mathbf{O}'}, + 119823: {LaTeXMode.textmode: '\\mathbf{P}'}, + 119824: {LaTeXMode.textmode: '\\mathbf{Q}'}, + 119825: {LaTeXMode.textmode: '\\mathbf{R}'}, + 119826: {LaTeXMode.textmode: '\\mathbf{S}'}, + 119827: {LaTeXMode.textmode: '\\mathbf{T}'}, + 119828: {LaTeXMode.textmode: '\\mathbf{U}'}, + 119829: {LaTeXMode.textmode: '\\mathbf{V}'}, + 119830: {LaTeXMode.textmode: '\\mathbf{W}'}, + 119831: {LaTeXMode.textmode: '\\mathbf{X}'}, + 119832: {LaTeXMode.textmode: '\\mathbf{Y}'}, + 119833: {LaTeXMode.textmode: '\\mathbf{Z}'}, + 119834: {LaTeXMode.textmode: '\\mathbf{a}'}, + 119835: {LaTeXMode.textmode: '\\mathbf{b}'}, + 119836: {LaTeXMode.textmode: '\\mathbf{c}'}, + 119837: {LaTeXMode.textmode: '\\mathbf{d}'}, + 119838: {LaTeXMode.textmode: '\\mathbf{e}'}, + 119839: {LaTeXMode.textmode: '\\mathbf{f}'}, + 119840: {LaTeXMode.textmode: '\\mathbf{g}'}, + 119841: {LaTeXMode.textmode: '\\mathbf{h}'}, + 119842: {LaTeXMode.textmode: '\\mathbf{i}'}, + 119843: {LaTeXMode.textmode: '\\mathbf{j}'}, + 119844: {LaTeXMode.textmode: '\\mathbf{k}'}, + 119845: {LaTeXMode.textmode: '\\mathbf{l}'}, + 119846: {LaTeXMode.textmode: '\\mathbf{m}'}, + 119847: {LaTeXMode.textmode: '\\mathbf{n}'}, + 119848: {LaTeXMode.textmode: '\\mathbf{o}'}, + 119849: {LaTeXMode.textmode: '\\mathbf{p}'}, + 119850: {LaTeXMode.textmode: '\\mathbf{q}'}, + 119851: {LaTeXMode.textmode: '\\mathbf{r}'}, + 119852: {LaTeXMode.textmode: '\\mathbf{s}'}, + 119853: {LaTeXMode.textmode: '\\mathbf{t}'}, + 119854: {LaTeXMode.textmode: '\\mathbf{u}'}, + 119855: {LaTeXMode.textmode: '\\mathbf{v}'}, + 119856: {LaTeXMode.textmode: '\\mathbf{w}'}, + 119857: {LaTeXMode.textmode: '\\mathbf{x}'}, + 119858: {LaTeXMode.textmode: '\\mathbf{y}'}, + 119859: {LaTeXMode.textmode: '\\mathbf{z}'}, + 119860: {LaTeXMode.textmode: '\\mathmit{A}'}, + 119861: {LaTeXMode.textmode: '\\mathmit{B}'}, + 119862: {LaTeXMode.textmode: '\\mathmit{C}'}, + 119863: {LaTeXMode.textmode: '\\mathmit{D}'}, + 119864: {LaTeXMode.textmode: '\\mathmit{E}'}, + 119865: {LaTeXMode.textmode: '\\mathmit{F}'}, + 119866: {LaTeXMode.textmode: '\\mathmit{G}'}, + 119867: {LaTeXMode.textmode: '\\mathmit{H}'}, + 119868: {LaTeXMode.textmode: '\\mathmit{I}'}, + 119869: {LaTeXMode.textmode: '\\mathmit{J}'}, + 119870: {LaTeXMode.textmode: '\\mathmit{K}'}, + 119871: {LaTeXMode.textmode: '\\mathmit{L}'}, + 119872: {LaTeXMode.textmode: '\\mathmit{M}'}, + 119873: {LaTeXMode.textmode: '\\mathmit{N}'}, + 119874: {LaTeXMode.textmode: '\\mathmit{O}'}, + 119875: {LaTeXMode.textmode: '\\mathmit{P}'}, + 119876: {LaTeXMode.textmode: '\\mathmit{Q}'}, + 119877: {LaTeXMode.textmode: '\\mathmit{R}'}, + 119878: {LaTeXMode.textmode: '\\mathmit{S}'}, + 119879: {LaTeXMode.textmode: '\\mathmit{T}'}, + 119880: {LaTeXMode.textmode: '\\mathmit{U}'}, + 119881: {LaTeXMode.textmode: '\\mathmit{V}'}, + 119882: {LaTeXMode.textmode: '\\mathmit{W}'}, + 119883: {LaTeXMode.textmode: '\\mathmit{X}'}, + 119884: {LaTeXMode.textmode: '\\mathmit{Y}'}, + 119885: {LaTeXMode.textmode: '\\mathmit{Z}'}, + 119886: {LaTeXMode.textmode: '\\mathmit{a}'}, + 119887: {LaTeXMode.textmode: '\\mathmit{b}'}, + 119888: {LaTeXMode.textmode: '\\mathmit{c}'}, + 119889: {LaTeXMode.textmode: '\\mathmit{d}'}, + 119890: {LaTeXMode.textmode: '\\mathmit{e}'}, + 119891: {LaTeXMode.textmode: '\\mathmit{f}'}, + 119892: {LaTeXMode.textmode: '\\mathmit{g}'}, + 119894: {LaTeXMode.textmode: '\\mathmit{i}'}, + 119895: {LaTeXMode.textmode: '\\mathmit{j}'}, + 119896: {LaTeXMode.textmode: '\\mathmit{k}'}, + 119897: {LaTeXMode.textmode: '\\mathmit{l}'}, + 119898: {LaTeXMode.textmode: '\\mathmit{m}'}, + 119899: {LaTeXMode.textmode: '\\mathmit{n}'}, + 119900: {LaTeXMode.textmode: '\\mathmit{o}'}, + 119901: {LaTeXMode.textmode: '\\mathmit{p}'}, + 119902: {LaTeXMode.textmode: '\\mathmit{q}'}, + 119903: {LaTeXMode.textmode: '\\mathmit{r}'}, + 119904: {LaTeXMode.textmode: '\\mathmit{s}'}, + 119905: {LaTeXMode.textmode: '\\mathmit{t}'}, + 119906: {LaTeXMode.textmode: '\\mathmit{u}'}, + 119907: {LaTeXMode.textmode: '\\mathmit{v}'}, + 119908: {LaTeXMode.textmode: '\\mathmit{w}'}, + 119909: {LaTeXMode.textmode: '\\mathmit{x}'}, + 119910: {LaTeXMode.textmode: '\\mathmit{y}'}, + 119911: {LaTeXMode.textmode: '\\mathmit{z}'}, + 119912: {LaTeXMode.textmode: '\\mathbit{A}'}, + 119913: {LaTeXMode.textmode: '\\mathbit{B}'}, + 119914: {LaTeXMode.textmode: '\\mathbit{C}'}, + 119915: {LaTeXMode.textmode: '\\mathbit{D}'}, + 119916: {LaTeXMode.textmode: '\\mathbit{E}'}, + 119917: {LaTeXMode.textmode: '\\mathbit{F}'}, + 119918: {LaTeXMode.textmode: '\\mathbit{G}'}, + 119919: {LaTeXMode.textmode: '\\mathbit{H}'}, + 119920: {LaTeXMode.textmode: '\\mathbit{I}'}, + 119921: {LaTeXMode.textmode: '\\mathbit{J}'}, + 119922: {LaTeXMode.textmode: '\\mathbit{K}'}, + 119923: {LaTeXMode.textmode: '\\mathbit{L}'}, + 119924: {LaTeXMode.textmode: '\\mathbit{M}'}, + 119925: {LaTeXMode.textmode: '\\mathbit{N}'}, + 119926: {LaTeXMode.textmode: '\\mathbit{O}'}, + 119927: {LaTeXMode.textmode: '\\mathbit{P}'}, + 119928: {LaTeXMode.textmode: '\\mathbit{Q}'}, + 119929: {LaTeXMode.textmode: '\\mathbit{R}'}, + 119930: {LaTeXMode.textmode: '\\mathbit{S}'}, + 119931: {LaTeXMode.textmode: '\\mathbit{T}'}, + 119932: {LaTeXMode.textmode: '\\mathbit{U}'}, + 119933: {LaTeXMode.textmode: '\\mathbit{V}'}, + 119934: {LaTeXMode.textmode: '\\mathbit{W}'}, + 119935: {LaTeXMode.textmode: '\\mathbit{X}'}, + 119936: {LaTeXMode.textmode: '\\mathbit{Y}'}, + 119937: {LaTeXMode.textmode: '\\mathbit{Z}'}, + 119938: {LaTeXMode.textmode: '\\mathbit{a}'}, + 119939: {LaTeXMode.textmode: '\\mathbit{b}'}, + 119940: {LaTeXMode.textmode: '\\mathbit{c}'}, + 119941: {LaTeXMode.textmode: '\\mathbit{d}'}, + 119942: {LaTeXMode.textmode: '\\mathbit{e}'}, + 119943: {LaTeXMode.textmode: '\\mathbit{f}'}, + 119944: {LaTeXMode.textmode: '\\mathbit{g}'}, + 119945: {LaTeXMode.textmode: '\\mathbit{h}'}, + 119946: {LaTeXMode.textmode: '\\mathbit{i}'}, + 119947: {LaTeXMode.textmode: '\\mathbit{j}'}, + 119948: {LaTeXMode.textmode: '\\mathbit{k}'}, + 119949: {LaTeXMode.textmode: '\\mathbit{l}'}, + 119950: {LaTeXMode.textmode: '\\mathbit{m}'}, + 119951: {LaTeXMode.textmode: '\\mathbit{n}'}, + 119952: {LaTeXMode.textmode: '\\mathbit{o}'}, + 119953: {LaTeXMode.textmode: '\\mathbit{p}'}, + 119954: {LaTeXMode.textmode: '\\mathbit{q}'}, + 119955: {LaTeXMode.textmode: '\\mathbit{r}'}, + 119956: {LaTeXMode.textmode: '\\mathbit{s}'}, + 119957: {LaTeXMode.textmode: '\\mathbit{t}'}, + 119958: {LaTeXMode.textmode: '\\mathbit{u}'}, + 119959: {LaTeXMode.textmode: '\\mathbit{v}'}, + 119960: {LaTeXMode.textmode: '\\mathbit{w}'}, + 119961: {LaTeXMode.textmode: '\\mathbit{x}'}, + 119962: {LaTeXMode.textmode: '\\mathbit{y}'}, + 119963: {LaTeXMode.textmode: '\\mathbit{z}'}, + 119964: {LaTeXMode.textmode: '\\mathscr{A}'}, + 119966: {LaTeXMode.textmode: '\\mathscr{C}'}, + 119967: {LaTeXMode.textmode: '\\mathscr{D}'}, + 119970: {LaTeXMode.textmode: '\\mathscr{G}'}, + 119973: {LaTeXMode.textmode: '\\mathscr{J}'}, + 119974: {LaTeXMode.textmode: '\\mathscr{K}'}, + 119977: {LaTeXMode.textmode: '\\mathscr{N}'}, + 119978: {LaTeXMode.textmode: '\\mathscr{O}'}, + 119979: {LaTeXMode.textmode: '\\mathscr{P}'}, + 119980: {LaTeXMode.textmode: '\\mathscr{Q}'}, + 119982: {LaTeXMode.textmode: '\\mathscr{S}'}, + 119983: {LaTeXMode.textmode: '\\mathscr{T}'}, + 119984: {LaTeXMode.textmode: '\\mathscr{U}'}, + 119985: {LaTeXMode.textmode: '\\mathscr{V}'}, + 119986: {LaTeXMode.textmode: '\\mathscr{W}'}, + 119987: {LaTeXMode.textmode: '\\mathscr{X}'}, + 119988: {LaTeXMode.textmode: '\\mathscr{Y}'}, + 119989: {LaTeXMode.textmode: '\\mathscr{Z}'}, + 119990: {LaTeXMode.textmode: '\\mathscr{a}'}, + 119991: {LaTeXMode.textmode: '\\mathscr{b}'}, + 119992: {LaTeXMode.textmode: '\\mathscr{c}'}, + 119993: {LaTeXMode.textmode: '\\mathscr{d}'}, + 119995: {LaTeXMode.textmode: '\\mathscr{f}'}, + 119997: {LaTeXMode.textmode: '\\mathscr{h}'}, + 119998: {LaTeXMode.textmode: '\\mathscr{i}'}, + 119999: {LaTeXMode.textmode: '\\mathscr{j}'}, + 120000: {LaTeXMode.textmode: '\\mathscr{k}'}, + 120001: {LaTeXMode.textmode: '\\mathscr{l}'}, + 120002: {LaTeXMode.textmode: '\\mathscr{m}'}, + 120003: {LaTeXMode.textmode: '\\mathscr{n}'}, + 120005: {LaTeXMode.textmode: '\\mathscr{p}'}, + 120006: {LaTeXMode.textmode: '\\mathscr{q}'}, + 120007: {LaTeXMode.textmode: '\\mathscr{r}'}, + 120008: {LaTeXMode.textmode: '\\mathscr{s}'}, + 120009: {LaTeXMode.textmode: '\\mathscr{t}'}, + 120010: {LaTeXMode.textmode: '\\mathscr{u}'}, + 120011: {LaTeXMode.textmode: '\\mathscr{v}'}, + 120012: {LaTeXMode.textmode: '\\mathscr{w}'}, + 120013: {LaTeXMode.textmode: '\\mathscr{x}'}, + 120014: {LaTeXMode.textmode: '\\mathscr{y}'}, + 120015: {LaTeXMode.textmode: '\\mathscr{z}'}, + 120016: {LaTeXMode.textmode: '\\mathbcal{A}'}, + 120017: {LaTeXMode.textmode: '\\mathbcal{B}'}, + 120018: {LaTeXMode.textmode: '\\mathbcal{C}'}, + 120019: {LaTeXMode.textmode: '\\mathbcal{D}'}, + 120020: {LaTeXMode.textmode: '\\mathbcal{E}'}, + 120021: {LaTeXMode.textmode: '\\mathbcal{F}'}, + 120022: {LaTeXMode.textmode: '\\mathbcal{G}'}, + 120023: {LaTeXMode.textmode: '\\mathbcal{H}'}, + 120024: {LaTeXMode.textmode: '\\mathbcal{I}'}, + 120025: {LaTeXMode.textmode: '\\mathbcal{J}'}, + 120026: {LaTeXMode.textmode: '\\mathbcal{K}'}, + 120027: {LaTeXMode.textmode: '\\mathbcal{L}'}, + 120028: {LaTeXMode.textmode: '\\mathbcal{M}'}, + 120029: {LaTeXMode.textmode: '\\mathbcal{N}'}, + 120030: {LaTeXMode.textmode: '\\mathbcal{O}'}, + 120031: {LaTeXMode.textmode: '\\mathbcal{P}'}, + 120032: {LaTeXMode.textmode: '\\mathbcal{Q}'}, + 120033: {LaTeXMode.textmode: '\\mathbcal{R}'}, + 120034: {LaTeXMode.textmode: '\\mathbcal{S}'}, + 120035: {LaTeXMode.textmode: '\\mathbcal{T}'}, + 120036: {LaTeXMode.textmode: '\\mathbcal{U}'}, + 120037: {LaTeXMode.textmode: '\\mathbcal{V}'}, + 120038: {LaTeXMode.textmode: '\\mathbcal{W}'}, + 120039: {LaTeXMode.textmode: '\\mathbcal{X}'}, + 120040: {LaTeXMode.textmode: '\\mathbcal{Y}'}, + 120041: {LaTeXMode.textmode: '\\mathbcal{Z}'}, + 120042: {LaTeXMode.textmode: '\\mathbcal{a}'}, + 120043: {LaTeXMode.textmode: '\\mathbcal{b}'}, + 120044: {LaTeXMode.textmode: '\\mathbcal{c}'}, + 120045: {LaTeXMode.textmode: '\\mathbcal{d}'}, + 120046: {LaTeXMode.textmode: '\\mathbcal{e}'}, + 120047: {LaTeXMode.textmode: '\\mathbcal{f}'}, + 120048: {LaTeXMode.textmode: '\\mathbcal{g}'}, + 120049: {LaTeXMode.textmode: '\\mathbcal{h}'}, + 120050: {LaTeXMode.textmode: '\\mathbcal{i}'}, + 120051: {LaTeXMode.textmode: '\\mathbcal{j}'}, + 120052: {LaTeXMode.textmode: '\\mathbcal{k}'}, + 120053: {LaTeXMode.textmode: '\\mathbcal{l}'}, + 120054: {LaTeXMode.textmode: '\\mathbcal{m}'}, + 120055: {LaTeXMode.textmode: '\\mathbcal{n}'}, + 120056: {LaTeXMode.textmode: '\\mathbcal{o}'}, + 120057: {LaTeXMode.textmode: '\\mathbcal{p}'}, + 120058: {LaTeXMode.textmode: '\\mathbcal{q}'}, + 120059: {LaTeXMode.textmode: '\\mathbcal{r}'}, + 120060: {LaTeXMode.textmode: '\\mathbcal{s}'}, + 120061: {LaTeXMode.textmode: '\\mathbcal{t}'}, + 120062: {LaTeXMode.textmode: '\\mathbcal{u}'}, + 120063: {LaTeXMode.textmode: '\\mathbcal{v}'}, + 120064: {LaTeXMode.textmode: '\\mathbcal{w}'}, + 120065: {LaTeXMode.textmode: '\\mathbcal{x}'}, + 120066: {LaTeXMode.textmode: '\\mathbcal{y}'}, + 120067: {LaTeXMode.textmode: '\\mathbcal{z}'}, + 120068: {LaTeXMode.textmode: '\\mathfrak{A}'}, + 120069: {LaTeXMode.textmode: '\\mathfrak{B}'}, + 120071: {LaTeXMode.textmode: '\\mathfrak{D}'}, + 120072: {LaTeXMode.textmode: '\\mathfrak{E}'}, + 120073: {LaTeXMode.textmode: '\\mathfrak{F}'}, + 120074: {LaTeXMode.textmode: '\\mathfrak{G}'}, + 120077: {LaTeXMode.textmode: '\\mathfrak{J}'}, + 120078: {LaTeXMode.textmode: '\\mathfrak{K}'}, + 120079: {LaTeXMode.textmode: '\\mathfrak{L}'}, + 120080: {LaTeXMode.textmode: '\\mathfrak{M}'}, + 120081: {LaTeXMode.textmode: '\\mathfrak{N}'}, + 120082: {LaTeXMode.textmode: '\\mathfrak{O}'}, + 120083: {LaTeXMode.textmode: '\\mathfrak{P}'}, + 120084: {LaTeXMode.textmode: '\\mathfrak{Q}'}, + 120086: {LaTeXMode.textmode: '\\mathfrak{S}'}, + 120087: {LaTeXMode.textmode: '\\mathfrak{T}'}, + 120088: {LaTeXMode.textmode: '\\mathfrak{U}'}, + 120089: {LaTeXMode.textmode: '\\mathfrak{V}'}, + 120090: {LaTeXMode.textmode: '\\mathfrak{W}'}, + 120091: {LaTeXMode.textmode: '\\mathfrak{X}'}, + 120092: {LaTeXMode.textmode: '\\mathfrak{Y}'}, + 120094: {LaTeXMode.textmode: '\\mathfrak{a}'}, + 120095: {LaTeXMode.textmode: '\\mathfrak{b}'}, + 120096: {LaTeXMode.textmode: '\\mathfrak{c}'}, + 120097: {LaTeXMode.textmode: '\\mathfrak{d}'}, + 120098: {LaTeXMode.textmode: '\\mathfrak{e}'}, + 120099: {LaTeXMode.textmode: '\\mathfrak{f}'}, + 120100: {LaTeXMode.textmode: '\\mathfrak{g}'}, + 120101: {LaTeXMode.textmode: '\\mathfrak{h}'}, + 120102: {LaTeXMode.textmode: '\\mathfrak{i}'}, + 120103: {LaTeXMode.textmode: '\\mathfrak{j}'}, + 120104: {LaTeXMode.textmode: '\\mathfrak{k}'}, + 120105: {LaTeXMode.textmode: '\\mathfrak{l}'}, + 120106: {LaTeXMode.textmode: '\\mathfrak{m}'}, + 120107: {LaTeXMode.textmode: '\\mathfrak{n}'}, + 120108: {LaTeXMode.textmode: '\\mathfrak{o}'}, + 120109: {LaTeXMode.textmode: '\\mathfrak{p}'}, + 120110: {LaTeXMode.textmode: '\\mathfrak{q}'}, + 120111: {LaTeXMode.textmode: '\\mathfrak{r}'}, + 120112: {LaTeXMode.textmode: '\\mathfrak{s}'}, + 120113: {LaTeXMode.textmode: '\\mathfrak{t}'}, + 120114: {LaTeXMode.textmode: '\\mathfrak{u}'}, + 120115: {LaTeXMode.textmode: '\\mathfrak{v}'}, + 120116: {LaTeXMode.textmode: '\\mathfrak{w}'}, + 120117: {LaTeXMode.textmode: '\\mathfrak{x}'}, + 120118: {LaTeXMode.textmode: '\\mathfrak{y}'}, + 120119: {LaTeXMode.textmode: '\\mathfrak{z}'}, + 120120: {LaTeXMode.textmode: '\\mathbb{A}'}, + 120121: {LaTeXMode.textmode: '\\mathbb{B}'}, + 120123: {LaTeXMode.textmode: '\\mathbb{D}'}, + 120124: {LaTeXMode.textmode: '\\mathbb{E}'}, + 120125: {LaTeXMode.textmode: '\\mathbb{F}'}, + 120126: {LaTeXMode.textmode: '\\mathbb{G}'}, + 120128: {LaTeXMode.textmode: '\\mathbb{I}'}, + 120129: {LaTeXMode.textmode: '\\mathbb{J}'}, + 120130: {LaTeXMode.textmode: '\\mathbb{K}'}, + 120131: {LaTeXMode.textmode: '\\mathbb{L}'}, + 120132: {LaTeXMode.textmode: '\\mathbb{M}'}, + 120134: {LaTeXMode.textmode: '\\mathbb{O}'}, + 120138: {LaTeXMode.textmode: '\\mathbb{S}'}, + 120139: {LaTeXMode.textmode: '\\mathbb{T}'}, + 120140: {LaTeXMode.textmode: '\\mathbb{U}'}, + 120141: {LaTeXMode.textmode: '\\mathbb{V}'}, + 120142: {LaTeXMode.textmode: '\\mathbb{W}'}, + 120143: {LaTeXMode.textmode: '\\mathbb{X}'}, + 120144: {LaTeXMode.textmode: '\\mathbb{Y}'}, + 120146: {LaTeXMode.textmode: '\\mathbb{a}'}, + 120147: {LaTeXMode.textmode: '\\mathbb{b}'}, + 120148: {LaTeXMode.textmode: '\\mathbb{c}'}, + 120149: {LaTeXMode.textmode: '\\mathbb{d}'}, + 120150: {LaTeXMode.textmode: '\\mathbb{e}'}, + 120151: {LaTeXMode.textmode: '\\mathbb{f}'}, + 120152: {LaTeXMode.textmode: '\\mathbb{g}'}, + 120153: {LaTeXMode.textmode: '\\mathbb{h}'}, + 120154: {LaTeXMode.textmode: '\\mathbb{i}'}, + 120155: {LaTeXMode.textmode: '\\mathbb{j}'}, + 120156: {LaTeXMode.textmode: '\\mathbb{k}'}, + 120157: {LaTeXMode.textmode: '\\mathbb{l}'}, + 120158: {LaTeXMode.textmode: '\\mathbb{m}'}, + 120159: {LaTeXMode.textmode: '\\mathbb{n}'}, + 120160: {LaTeXMode.textmode: '\\mathbb{o}'}, + 120161: {LaTeXMode.textmode: '\\mathbb{p}'}, + 120162: {LaTeXMode.textmode: '\\mathbb{q}'}, + 120163: {LaTeXMode.textmode: '\\mathbb{r}'}, + 120164: {LaTeXMode.textmode: '\\mathbb{s}'}, + 120165: {LaTeXMode.textmode: '\\mathbb{t}'}, + 120166: {LaTeXMode.textmode: '\\mathbb{u}'}, + 120167: {LaTeXMode.textmode: '\\mathbb{v}'}, + 120168: {LaTeXMode.textmode: '\\mathbb{w}'}, + 120169: {LaTeXMode.textmode: '\\mathbb{x}'}, + 120170: {LaTeXMode.textmode: '\\mathbb{y}'}, + 120171: {LaTeXMode.textmode: '\\mathbb{z}'}, + 120172: {LaTeXMode.textmode: '\\mathbfrak{A}'}, + 120173: {LaTeXMode.textmode: '\\mathbfrak{B}'}, + 120174: {LaTeXMode.textmode: '\\mathbfrak{C}'}, + 120175: {LaTeXMode.textmode: '\\mathbfrak{D}'}, + 120176: {LaTeXMode.textmode: '\\mathbfrak{E}'}, + 120177: {LaTeXMode.textmode: '\\mathbfrak{F}'}, + 120178: {LaTeXMode.textmode: '\\mathbfrak{G}'}, + 120179: {LaTeXMode.textmode: '\\mathbfrak{H}'}, + 120180: {LaTeXMode.textmode: '\\mathbfrak{I}'}, + 120181: {LaTeXMode.textmode: '\\mathbfrak{J}'}, + 120182: {LaTeXMode.textmode: '\\mathbfrak{K}'}, + 120183: {LaTeXMode.textmode: '\\mathbfrak{L}'}, + 120184: {LaTeXMode.textmode: '\\mathbfrak{M}'}, + 120185: {LaTeXMode.textmode: '\\mathbfrak{N}'}, + 120186: {LaTeXMode.textmode: '\\mathbfrak{O}'}, + 120187: {LaTeXMode.textmode: '\\mathbfrak{P}'}, + 120188: {LaTeXMode.textmode: '\\mathbfrak{Q}'}, + 120189: {LaTeXMode.textmode: '\\mathbfrak{R}'}, + 120190: {LaTeXMode.textmode: '\\mathbfrak{S}'}, + 120191: {LaTeXMode.textmode: '\\mathbfrak{T}'}, + 120192: {LaTeXMode.textmode: '\\mathbfrak{U}'}, + 120193: {LaTeXMode.textmode: '\\mathbfrak{V}'}, + 120194: {LaTeXMode.textmode: '\\mathbfrak{W}'}, + 120195: {LaTeXMode.textmode: '\\mathbfrak{X}'}, + 120196: {LaTeXMode.textmode: '\\mathbfrak{Y}'}, + 120197: {LaTeXMode.textmode: '\\mathbfrak{Z}'}, + 120198: {LaTeXMode.textmode: '\\mathbfrak{a}'}, + 120199: {LaTeXMode.textmode: '\\mathbfrak{b}'}, + 120200: {LaTeXMode.textmode: '\\mathbfrak{c}'}, + 120201: {LaTeXMode.textmode: '\\mathbfrak{d}'}, + 120202: {LaTeXMode.textmode: '\\mathbfrak{e}'}, + 120203: {LaTeXMode.textmode: '\\mathbfrak{f}'}, + 120204: {LaTeXMode.textmode: '\\mathbfrak{g}'}, + 120205: {LaTeXMode.textmode: '\\mathbfrak{h}'}, + 120206: {LaTeXMode.textmode: '\\mathbfrak{i}'}, + 120207: {LaTeXMode.textmode: '\\mathbfrak{j}'}, + 120208: {LaTeXMode.textmode: '\\mathbfrak{k}'}, + 120209: {LaTeXMode.textmode: '\\mathbfrak{l}'}, + 120210: {LaTeXMode.textmode: '\\mathbfrak{m}'}, + 120211: {LaTeXMode.textmode: '\\mathbfrak{n}'}, + 120212: {LaTeXMode.textmode: '\\mathbfrak{o}'}, + 120213: {LaTeXMode.textmode: '\\mathbfrak{p}'}, + 120214: {LaTeXMode.textmode: '\\mathbfrak{q}'}, + 120215: {LaTeXMode.textmode: '\\mathbfrak{r}'}, + 120216: {LaTeXMode.textmode: '\\mathbfrak{s}'}, + 120217: {LaTeXMode.textmode: '\\mathbfrak{t}'}, + 120218: {LaTeXMode.textmode: '\\mathbfrak{u}'}, + 120219: {LaTeXMode.textmode: '\\mathbfrak{v}'}, + 120220: {LaTeXMode.textmode: '\\mathbfrak{w}'}, + 120221: {LaTeXMode.textmode: '\\mathbfrak{x}'}, + 120222: {LaTeXMode.textmode: '\\mathbfrak{y}'}, + 120223: {LaTeXMode.textmode: '\\mathbfrak{z}'}, + 120224: {LaTeXMode.textmode: '\\mathsf{A}'}, + 120225: {LaTeXMode.textmode: '\\mathsf{B}'}, + 120226: {LaTeXMode.textmode: '\\mathsf{C}'}, + 120227: {LaTeXMode.textmode: '\\mathsf{D}'}, + 120228: {LaTeXMode.textmode: '\\mathsf{E}'}, + 120229: {LaTeXMode.textmode: '\\mathsf{F}'}, + 120230: {LaTeXMode.textmode: '\\mathsf{G}'}, + 120231: {LaTeXMode.textmode: '\\mathsf{H}'}, + 120232: {LaTeXMode.textmode: '\\mathsf{I}'}, + 120233: {LaTeXMode.textmode: '\\mathsf{J}'}, + 120234: {LaTeXMode.textmode: '\\mathsf{K}'}, + 120235: {LaTeXMode.textmode: '\\mathsf{L}'}, + 120236: {LaTeXMode.textmode: '\\mathsf{M}'}, + 120237: {LaTeXMode.textmode: '\\mathsf{N}'}, + 120238: {LaTeXMode.textmode: '\\mathsf{O}'}, + 120239: {LaTeXMode.textmode: '\\mathsf{P}'}, + 120240: {LaTeXMode.textmode: '\\mathsf{Q}'}, + 120241: {LaTeXMode.textmode: '\\mathsf{R}'}, + 120242: {LaTeXMode.textmode: '\\mathsf{S}'}, + 120243: {LaTeXMode.textmode: '\\mathsf{T}'}, + 120244: {LaTeXMode.textmode: '\\mathsf{U}'}, + 120245: {LaTeXMode.textmode: '\\mathsf{V}'}, + 120246: {LaTeXMode.textmode: '\\mathsf{W}'}, + 120247: {LaTeXMode.textmode: '\\mathsf{X}'}, + 120248: {LaTeXMode.textmode: '\\mathsf{Y}'}, + 120249: {LaTeXMode.textmode: '\\mathsf{Z}'}, + 120250: {LaTeXMode.textmode: '\\mathsf{a}'}, + 120251: {LaTeXMode.textmode: '\\mathsf{b}'}, + 120252: {LaTeXMode.textmode: '\\mathsf{c}'}, + 120253: {LaTeXMode.textmode: '\\mathsf{d}'}, + 120254: {LaTeXMode.textmode: '\\mathsf{e}'}, + 120255: {LaTeXMode.textmode: '\\mathsf{f}'}, + 120256: {LaTeXMode.textmode: '\\mathsf{g}'}, + 120257: {LaTeXMode.textmode: '\\mathsf{h}'}, + 120258: {LaTeXMode.textmode: '\\mathsf{i}'}, + 120259: {LaTeXMode.textmode: '\\mathsf{j}'}, + 120260: {LaTeXMode.textmode: '\\mathsf{k}'}, + 120261: {LaTeXMode.textmode: '\\mathsf{l}'}, + 120262: {LaTeXMode.textmode: '\\mathsf{m}'}, + 120263: {LaTeXMode.textmode: '\\mathsf{n}'}, + 120264: {LaTeXMode.textmode: '\\mathsf{o}'}, + 120265: {LaTeXMode.textmode: '\\mathsf{p}'}, + 120266: {LaTeXMode.textmode: '\\mathsf{q}'}, + 120267: {LaTeXMode.textmode: '\\mathsf{r}'}, + 120268: {LaTeXMode.textmode: '\\mathsf{s}'}, + 120269: {LaTeXMode.textmode: '\\mathsf{t}'}, + 120270: {LaTeXMode.textmode: '\\mathsf{u}'}, + 120271: {LaTeXMode.textmode: '\\mathsf{v}'}, + 120272: {LaTeXMode.textmode: '\\mathsf{w}'}, + 120273: {LaTeXMode.textmode: '\\mathsf{x}'}, + 120274: {LaTeXMode.textmode: '\\mathsf{y}'}, + 120275: {LaTeXMode.textmode: '\\mathsf{z}'}, + 120276: {LaTeXMode.textmode: '\\mathsfbf{A}'}, + 120277: {LaTeXMode.textmode: '\\mathsfbf{B}'}, + 120278: {LaTeXMode.textmode: '\\mathsfbf{C}'}, + 120279: {LaTeXMode.textmode: '\\mathsfbf{D}'}, + 120280: {LaTeXMode.textmode: '\\mathsfbf{E}'}, + 120281: {LaTeXMode.textmode: '\\mathsfbf{F}'}, + 120282: {LaTeXMode.textmode: '\\mathsfbf{G}'}, + 120283: {LaTeXMode.textmode: '\\mathsfbf{H}'}, + 120284: {LaTeXMode.textmode: '\\mathsfbf{I}'}, + 120285: {LaTeXMode.textmode: '\\mathsfbf{J}'}, + 120286: {LaTeXMode.textmode: '\\mathsfbf{K}'}, + 120287: {LaTeXMode.textmode: '\\mathsfbf{L}'}, + 120288: {LaTeXMode.textmode: '\\mathsfbf{M}'}, + 120289: {LaTeXMode.textmode: '\\mathsfbf{N}'}, + 120290: {LaTeXMode.textmode: '\\mathsfbf{O}'}, + 120291: {LaTeXMode.textmode: '\\mathsfbf{P}'}, + 120292: {LaTeXMode.textmode: '\\mathsfbf{Q}'}, + 120293: {LaTeXMode.textmode: '\\mathsfbf{R}'}, + 120294: {LaTeXMode.textmode: '\\mathsfbf{S}'}, + 120295: {LaTeXMode.textmode: '\\mathsfbf{T}'}, + 120296: {LaTeXMode.textmode: '\\mathsfbf{U}'}, + 120297: {LaTeXMode.textmode: '\\mathsfbf{V}'}, + 120298: {LaTeXMode.textmode: '\\mathsfbf{W}'}, + 120299: {LaTeXMode.textmode: '\\mathsfbf{X}'}, + 120300: {LaTeXMode.textmode: '\\mathsfbf{Y}'}, + 120301: {LaTeXMode.textmode: '\\mathsfbf{Z}'}, + 120302: {LaTeXMode.textmode: '\\mathsfbf{a}'}, + 120303: {LaTeXMode.textmode: '\\mathsfbf{b}'}, + 120304: {LaTeXMode.textmode: '\\mathsfbf{c}'}, + 120305: {LaTeXMode.textmode: '\\mathsfbf{d}'}, + 120306: {LaTeXMode.textmode: '\\mathsfbf{e}'}, + 120307: {LaTeXMode.textmode: '\\mathsfbf{f}'}, + 120308: {LaTeXMode.textmode: '\\mathsfbf{g}'}, + 120309: {LaTeXMode.textmode: '\\mathsfbf{h}'}, + 120310: {LaTeXMode.textmode: '\\mathsfbf{i}'}, + 120311: {LaTeXMode.textmode: '\\mathsfbf{j}'}, + 120312: {LaTeXMode.textmode: '\\mathsfbf{k}'}, + 120313: {LaTeXMode.textmode: '\\mathsfbf{l}'}, + 120314: {LaTeXMode.textmode: '\\mathsfbf{m}'}, + 120315: {LaTeXMode.textmode: '\\mathsfbf{n}'}, + 120316: {LaTeXMode.textmode: '\\mathsfbf{o}'}, + 120317: {LaTeXMode.textmode: '\\mathsfbf{p}'}, + 120318: {LaTeXMode.textmode: '\\mathsfbf{q}'}, + 120319: {LaTeXMode.textmode: '\\mathsfbf{r}'}, + 120320: {LaTeXMode.textmode: '\\mathsfbf{s}'}, + 120321: {LaTeXMode.textmode: '\\mathsfbf{t}'}, + 120322: {LaTeXMode.textmode: '\\mathsfbf{u}'}, + 120323: {LaTeXMode.textmode: '\\mathsfbf{v}'}, + 120324: {LaTeXMode.textmode: '\\mathsfbf{w}'}, + 120325: {LaTeXMode.textmode: '\\mathsfbf{x}'}, + 120326: {LaTeXMode.textmode: '\\mathsfbf{y}'}, + 120327: {LaTeXMode.textmode: '\\mathsfbf{z}'}, + 120328: {LaTeXMode.textmode: '\\mathsfsl{A}'}, + 120329: {LaTeXMode.textmode: '\\mathsfsl{B}'}, + 120330: {LaTeXMode.textmode: '\\mathsfsl{C}'}, + 120331: {LaTeXMode.textmode: '\\mathsfsl{D}'}, + 120332: {LaTeXMode.textmode: '\\mathsfsl{E}'}, + 120333: {LaTeXMode.textmode: '\\mathsfsl{F}'}, + 120334: {LaTeXMode.textmode: '\\mathsfsl{G}'}, + 120335: {LaTeXMode.textmode: '\\mathsfsl{H}'}, + 120336: {LaTeXMode.textmode: '\\mathsfsl{I}'}, + 120337: {LaTeXMode.textmode: '\\mathsfsl{J}'}, + 120338: {LaTeXMode.textmode: '\\mathsfsl{K}'}, + 120339: {LaTeXMode.textmode: '\\mathsfsl{L}'}, + 120340: {LaTeXMode.textmode: '\\mathsfsl{M}'}, + 120341: {LaTeXMode.textmode: '\\mathsfsl{N}'}, + 120342: {LaTeXMode.textmode: '\\mathsfsl{O}'}, + 120343: {LaTeXMode.textmode: '\\mathsfsl{P}'}, + 120344: {LaTeXMode.textmode: '\\mathsfsl{Q}'}, + 120345: {LaTeXMode.textmode: '\\mathsfsl{R}'}, + 120346: {LaTeXMode.textmode: '\\mathsfsl{S}'}, + 120347: {LaTeXMode.textmode: '\\mathsfsl{T}'}, + 120348: {LaTeXMode.textmode: '\\mathsfsl{U}'}, + 120349: {LaTeXMode.textmode: '\\mathsfsl{V}'}, + 120350: {LaTeXMode.textmode: '\\mathsfsl{W}'}, + 120351: {LaTeXMode.textmode: '\\mathsfsl{X}'}, + 120352: {LaTeXMode.textmode: '\\mathsfsl{Y}'}, + 120353: {LaTeXMode.textmode: '\\mathsfsl{Z}'}, + 120354: {LaTeXMode.textmode: '\\mathsfsl{a}'}, + 120355: {LaTeXMode.textmode: '\\mathsfsl{b}'}, + 120356: {LaTeXMode.textmode: '\\mathsfsl{c}'}, + 120357: {LaTeXMode.textmode: '\\mathsfsl{d}'}, + 120358: {LaTeXMode.textmode: '\\mathsfsl{e}'}, + 120359: {LaTeXMode.textmode: '\\mathsfsl{f}'}, + 120360: {LaTeXMode.textmode: '\\mathsfsl{g}'}, + 120361: {LaTeXMode.textmode: '\\mathsfsl{h}'}, + 120362: {LaTeXMode.textmode: '\\mathsfsl{i}'}, + 120363: {LaTeXMode.textmode: '\\mathsfsl{j}'}, + 120364: {LaTeXMode.textmode: '\\mathsfsl{k}'}, + 120365: {LaTeXMode.textmode: '\\mathsfsl{l}'}, + 120366: {LaTeXMode.textmode: '\\mathsfsl{m}'}, + 120367: {LaTeXMode.textmode: '\\mathsfsl{n}'}, + 120368: {LaTeXMode.textmode: '\\mathsfsl{o}'}, + 120369: {LaTeXMode.textmode: '\\mathsfsl{p}'}, + 120370: {LaTeXMode.textmode: '\\mathsfsl{q}'}, + 120371: {LaTeXMode.textmode: '\\mathsfsl{r}'}, + 120372: {LaTeXMode.textmode: '\\mathsfsl{s}'}, + 120373: {LaTeXMode.textmode: '\\mathsfsl{t}'}, + 120374: {LaTeXMode.textmode: '\\mathsfsl{u}'}, + 120375: {LaTeXMode.textmode: '\\mathsfsl{v}'}, + 120376: {LaTeXMode.textmode: '\\mathsfsl{w}'}, + 120377: {LaTeXMode.textmode: '\\mathsfsl{x}'}, + 120378: {LaTeXMode.textmode: '\\mathsfsl{y}'}, + 120379: {LaTeXMode.textmode: '\\mathsfsl{z}'}, + 120380: {LaTeXMode.textmode: '\\mathsfbfsl{A}'}, + 120381: {LaTeXMode.textmode: '\\mathsfbfsl{B}'}, + 120382: {LaTeXMode.textmode: '\\mathsfbfsl{C}'}, + 120383: {LaTeXMode.textmode: '\\mathsfbfsl{D}'}, + 120384: {LaTeXMode.textmode: '\\mathsfbfsl{E}'}, + 120385: {LaTeXMode.textmode: '\\mathsfbfsl{F}'}, + 120386: {LaTeXMode.textmode: '\\mathsfbfsl{G}'}, + 120387: {LaTeXMode.textmode: '\\mathsfbfsl{H}'}, + 120388: {LaTeXMode.textmode: '\\mathsfbfsl{I}'}, + 120389: {LaTeXMode.textmode: '\\mathsfbfsl{J}'}, + 120390: {LaTeXMode.textmode: '\\mathsfbfsl{K}'}, + 120391: {LaTeXMode.textmode: '\\mathsfbfsl{L}'}, + 120392: {LaTeXMode.textmode: '\\mathsfbfsl{M}'}, + 120393: {LaTeXMode.textmode: '\\mathsfbfsl{N}'}, + 120394: {LaTeXMode.textmode: '\\mathsfbfsl{O}'}, + 120395: {LaTeXMode.textmode: '\\mathsfbfsl{P}'}, + 120396: {LaTeXMode.textmode: '\\mathsfbfsl{Q}'}, + 120397: {LaTeXMode.textmode: '\\mathsfbfsl{R}'}, + 120398: {LaTeXMode.textmode: '\\mathsfbfsl{S}'}, + 120399: {LaTeXMode.textmode: '\\mathsfbfsl{T}'}, + 120400: {LaTeXMode.textmode: '\\mathsfbfsl{U}'}, + 120401: {LaTeXMode.textmode: '\\mathsfbfsl{V}'}, + 120402: {LaTeXMode.textmode: '\\mathsfbfsl{W}'}, + 120403: {LaTeXMode.textmode: '\\mathsfbfsl{X}'}, + 120404: {LaTeXMode.textmode: '\\mathsfbfsl{Y}'}, + 120405: {LaTeXMode.textmode: '\\mathsfbfsl{Z}'}, + 120406: {LaTeXMode.textmode: '\\mathsfbfsl{a}'}, + 120407: {LaTeXMode.textmode: '\\mathsfbfsl{b}'}, + 120408: {LaTeXMode.textmode: '\\mathsfbfsl{c}'}, + 120409: {LaTeXMode.textmode: '\\mathsfbfsl{d}'}, + 120410: {LaTeXMode.textmode: '\\mathsfbfsl{e}'}, + 120411: {LaTeXMode.textmode: '\\mathsfbfsl{f}'}, + 120412: {LaTeXMode.textmode: '\\mathsfbfsl{g}'}, + 120413: {LaTeXMode.textmode: '\\mathsfbfsl{h}'}, + 120414: {LaTeXMode.textmode: '\\mathsfbfsl{i}'}, + 120415: {LaTeXMode.textmode: '\\mathsfbfsl{j}'}, + 120416: {LaTeXMode.textmode: '\\mathsfbfsl{k}'}, + 120417: {LaTeXMode.textmode: '\\mathsfbfsl{l}'}, + 120418: {LaTeXMode.textmode: '\\mathsfbfsl{m}'}, + 120419: {LaTeXMode.textmode: '\\mathsfbfsl{n}'}, + 120420: {LaTeXMode.textmode: '\\mathsfbfsl{o}'}, + 120421: {LaTeXMode.textmode: '\\mathsfbfsl{p}'}, + 120422: {LaTeXMode.textmode: '\\mathsfbfsl{q}'}, + 120423: {LaTeXMode.textmode: '\\mathsfbfsl{r}'}, + 120424: {LaTeXMode.textmode: '\\mathsfbfsl{s}'}, + 120425: {LaTeXMode.textmode: '\\mathsfbfsl{t}'}, + 120426: {LaTeXMode.textmode: '\\mathsfbfsl{u}'}, + 120427: {LaTeXMode.textmode: '\\mathsfbfsl{v}'}, + 120428: {LaTeXMode.textmode: '\\mathsfbfsl{w}'}, + 120429: {LaTeXMode.textmode: '\\mathsfbfsl{x}'}, + 120430: {LaTeXMode.textmode: '\\mathsfbfsl{y}'}, + 120431: {LaTeXMode.textmode: '\\mathsfbfsl{z}'}, + 120432: {LaTeXMode.textmode: '\\mathtt{A}'}, + 120433: {LaTeXMode.textmode: '\\mathtt{B}'}, + 120434: {LaTeXMode.textmode: '\\mathtt{C}'}, + 120435: {LaTeXMode.textmode: '\\mathtt{D}'}, + 120436: {LaTeXMode.textmode: '\\mathtt{E}'}, + 120437: {LaTeXMode.textmode: '\\mathtt{F}'}, + 120438: {LaTeXMode.textmode: '\\mathtt{G}'}, + 120439: {LaTeXMode.textmode: '\\mathtt{H}'}, + 120440: {LaTeXMode.textmode: '\\mathtt{I}'}, + 120441: {LaTeXMode.textmode: '\\mathtt{J}'}, + 120442: {LaTeXMode.textmode: '\\mathtt{K}'}, + 120443: {LaTeXMode.textmode: '\\mathtt{L}'}, + 120444: {LaTeXMode.textmode: '\\mathtt{M}'}, + 120445: {LaTeXMode.textmode: '\\mathtt{N}'}, + 120446: {LaTeXMode.textmode: '\\mathtt{O}'}, + 120447: {LaTeXMode.textmode: '\\mathtt{P}'}, + 120448: {LaTeXMode.textmode: '\\mathtt{Q}'}, + 120449: {LaTeXMode.textmode: '\\mathtt{R}'}, + 120450: {LaTeXMode.textmode: '\\mathtt{S}'}, + 120451: {LaTeXMode.textmode: '\\mathtt{T}'}, + 120452: {LaTeXMode.textmode: '\\mathtt{U}'}, + 120453: {LaTeXMode.textmode: '\\mathtt{V}'}, + 120454: {LaTeXMode.textmode: '\\mathtt{W}'}, + 120455: {LaTeXMode.textmode: '\\mathtt{X}'}, + 120456: {LaTeXMode.textmode: '\\mathtt{Y}'}, + 120457: {LaTeXMode.textmode: '\\mathtt{Z}'}, + 120458: {LaTeXMode.textmode: '\\mathtt{a}'}, + 120459: {LaTeXMode.textmode: '\\mathtt{b}'}, + 120460: {LaTeXMode.textmode: '\\mathtt{c}'}, + 120461: {LaTeXMode.textmode: '\\mathtt{d}'}, + 120462: {LaTeXMode.textmode: '\\mathtt{e}'}, + 120463: {LaTeXMode.textmode: '\\mathtt{f}'}, + 120464: {LaTeXMode.textmode: '\\mathtt{g}'}, + 120465: {LaTeXMode.textmode: '\\mathtt{h}'}, + 120466: {LaTeXMode.textmode: '\\mathtt{i}'}, + 120467: {LaTeXMode.textmode: '\\mathtt{j}'}, + 120468: {LaTeXMode.textmode: '\\mathtt{k}'}, + 120469: {LaTeXMode.textmode: '\\mathtt{l}'}, + 120470: {LaTeXMode.textmode: '\\mathtt{m}'}, + 120471: {LaTeXMode.textmode: '\\mathtt{n}'}, + 120472: {LaTeXMode.textmode: '\\mathtt{o}'}, + 120473: {LaTeXMode.textmode: '\\mathtt{p}'}, + 120474: {LaTeXMode.textmode: '\\mathtt{q}'}, + 120475: {LaTeXMode.textmode: '\\mathtt{r}'}, + 120476: {LaTeXMode.textmode: '\\mathtt{s}'}, + 120477: {LaTeXMode.textmode: '\\mathtt{t}'}, + 120478: {LaTeXMode.textmode: '\\mathtt{u}'}, + 120479: {LaTeXMode.textmode: '\\mathtt{v}'}, + 120480: {LaTeXMode.textmode: '\\mathtt{w}'}, + 120481: {LaTeXMode.textmode: '\\mathtt{x}'}, + 120482: {LaTeXMode.textmode: '\\mathtt{y}'}, + 120483: {LaTeXMode.textmode: '\\mathtt{z}'}, + 120488: {LaTeXMode.textmode: '\\mathbf{\\Alpha}'}, + 120489: {LaTeXMode.textmode: '\\mathbf{\\Beta}'}, + 120490: {LaTeXMode.textmode: '\\mathbf{\\Gamma}'}, + 120491: {LaTeXMode.textmode: '\\mathbf{\\Delta}'}, + 120492: {LaTeXMode.textmode: '\\mathbf{\\Epsilon}'}, + 120493: {LaTeXMode.textmode: '\\mathbf{\\Zeta}'}, + 120494: {LaTeXMode.textmode: '\\mathbf{\\Eta}'}, + 120495: {LaTeXMode.textmode: '\\mathbf{\\Theta}'}, + 120496: {LaTeXMode.textmode: '\\mathbf{\\Iota}'}, + 120497: {LaTeXMode.textmode: '\\mathbf{\\Kappa}'}, + 120498: {LaTeXMode.textmode: '\\mathbf{\\Lambda}'}, + 120499: {LaTeXMode.textmode: '\\mathbf{M}'}, + 120500: {LaTeXMode.textmode: 'N'}, + 120501: {LaTeXMode.textmode: '\\mathbf{\\Xi}'}, + 120502: {LaTeXMode.textmode: 'O'}, + 120503: {LaTeXMode.textmode: '\\mathbf{\\Pi}'}, + 120504: {LaTeXMode.textmode: '\\mathbf{\\Rho}'}, + 120505: {LaTeXMode.textmode: '\\mathbf{\\vartheta}'}, + 120506: {LaTeXMode.textmode: '\\mathbf{\\Sigma}'}, + 120507: {LaTeXMode.textmode: '\\mathbf{\\Tau}'}, + 120508: {LaTeXMode.textmode: '\\mathbf{\\Upsilon}'}, + 120509: {LaTeXMode.textmode: '\\mathbf{\\Phi}'}, + 120510: {LaTeXMode.textmode: '\\mathbf{\\Chi}'}, + 120511: {LaTeXMode.textmode: '\\mathbf{\\Psi}'}, + 120512: {LaTeXMode.textmode: '\\mathbf{\\Omega}'}, + 120513: {LaTeXMode.textmode: '\\mathbf{\\nabla}'}, + 120514: {LaTeXMode.textmode: '\\mathbf{\\alpha}'}, + 120515: {LaTeXMode.textmode: '\\mathbf{\\beta}'}, + 120516: {LaTeXMode.textmode: '\\mathbf{\\gamma}'}, + 120517: {LaTeXMode.textmode: '\\mathbf{\\delta}'}, + 120518: {LaTeXMode.textmode: '\\mathbf{\\epsilon}'}, + 120519: {LaTeXMode.textmode: '\\mathbf{\\zeta}'}, + 120520: {LaTeXMode.textmode: '\\mathbf{\\eta}'}, + 120521: {LaTeXMode.textmode: '\\mathbf{\\theta}'}, + 120522: {LaTeXMode.textmode: '\\mathbf{\\iota}'}, + 120523: {LaTeXMode.textmode: '\\mathbf{\\kappa}'}, + 120524: {LaTeXMode.textmode: '\\mathbf{\\lambda}'}, + 120525: {LaTeXMode.textmode: '\\mathbf{\\mu}'}, + 120526: {LaTeXMode.textmode: '\\mathbf{\\nu}'}, + 120527: {LaTeXMode.textmode: '\\mathbf{\\xi}'}, + 120528: {LaTeXMode.textmode: '\\mathbf{o}'}, + 120529: {LaTeXMode.textmode: '\\mathbf{\\pi}'}, + 120530: {LaTeXMode.textmode: '\\mathbf{\\rho}'}, + 120531: {LaTeXMode.textmode: '\\mathbf{\\varsigma}'}, + 120532: {LaTeXMode.textmode: '\\mathbf{\\sigma}'}, + 120533: {LaTeXMode.textmode: '\\mathbf{\\tau}'}, + 120534: {LaTeXMode.textmode: '\\mathbf{\\upsilon}'}, + 120535: {LaTeXMode.textmode: '\\mathbf{\\phi}'}, + 120536: {LaTeXMode.textmode: '\\mathbf{\\chi}'}, + 120537: {LaTeXMode.textmode: '\\mathbf{\\psi}'}, + 120538: {LaTeXMode.textmode: '\\mathbf{\\omega}'}, + 120539: {LaTeXMode.textmode: '\\partial '}, + 120540: {LaTeXMode.textmode: '\\mathbf{\\varepsilon}'}, + 120541: {LaTeXMode.textmode: '\\mathbf{\\vartheta}'}, + 120542: {LaTeXMode.textmode: '\\mathbf{\\varkappa}'}, + 120543: {LaTeXMode.textmode: '\\mathbf{\\phi}'}, + 120544: {LaTeXMode.textmode: '\\mathbf{\\varrho}'}, + 120545: {LaTeXMode.textmode: '\\mathbf{\\varpi}'}, + 120546: {LaTeXMode.textmode: '\\mathmit{\\Alpha}'}, + 120547: {LaTeXMode.textmode: '\\mathmit{\\Beta}'}, + 120548: {LaTeXMode.textmode: '\\mathmit{\\Gamma}'}, + 120549: {LaTeXMode.textmode: '\\mathmit{\\Delta}'}, + 120550: {LaTeXMode.textmode: '\\mathmit{\\Epsilon}'}, + 120551: {LaTeXMode.textmode: '\\mathmit{\\Zeta}'}, + 120552: {LaTeXMode.textmode: '\\mathmit{\\Eta}'}, + 120553: {LaTeXMode.textmode: '\\mathmit{\\Theta}'}, + 120554: {LaTeXMode.textmode: '\\mathmit{\\Iota}'}, + 120555: {LaTeXMode.textmode: '\\mathmit{\\Kappa}'}, + 120556: {LaTeXMode.textmode: '\\mathmit{\\Lambda}'}, + 120557: {LaTeXMode.textmode: '\\mathmit{M}'}, + 120558: {LaTeXMode.textmode: 'N'}, + 120559: {LaTeXMode.textmode: '\\mathmit{\\Xi}'}, + 120560: {LaTeXMode.textmode: 'O'}, + 120561: {LaTeXMode.textmode: '\\mathmit{\\Pi}'}, + 120562: {LaTeXMode.textmode: '\\mathmit{\\Rho}'}, + 120563: {LaTeXMode.textmode: '\\mathmit{\\vartheta}'}, + 120564: {LaTeXMode.textmode: '\\mathmit{\\Sigma}'}, + 120565: {LaTeXMode.textmode: '\\mathmit{\\Tau}'}, + 120566: {LaTeXMode.textmode: '\\mathmit{\\Upsilon}'}, + 120567: {LaTeXMode.textmode: '\\mathmit{\\Phi}'}, + 120568: {LaTeXMode.textmode: '\\mathmit{\\Chi}'}, + 120569: {LaTeXMode.textmode: '\\mathmit{\\Psi}'}, + 120570: {LaTeXMode.textmode: '\\mathmit{\\Omega}'}, + 120571: {LaTeXMode.textmode: '\\mathmit{\\nabla}'}, + 120572: {LaTeXMode.textmode: '\\mathmit{\\alpha}'}, + 120573: {LaTeXMode.textmode: '\\mathmit{\\beta}'}, + 120574: {LaTeXMode.textmode: '\\mathmit{\\gamma}'}, + 120575: {LaTeXMode.textmode: '\\mathmit{\\delta}'}, + 120576: {LaTeXMode.textmode: '\\mathmit{\\epsilon}'}, + 120577: {LaTeXMode.textmode: '\\mathmit{\\zeta}'}, + 120578: {LaTeXMode.textmode: '\\mathmit{\\eta}'}, + 120579: {LaTeXMode.textmode: '\\mathmit{\\theta}'}, + 120580: {LaTeXMode.textmode: '\\mathmit{\\iota}'}, + 120581: {LaTeXMode.textmode: '\\mathmit{\\kappa}'}, + 120582: {LaTeXMode.textmode: '\\mathmit{\\lambda}'}, + 120583: {LaTeXMode.textmode: '\\mathmit{\\mu}'}, + 120584: {LaTeXMode.textmode: '\\mathmit{\\nu}'}, + 120585: {LaTeXMode.textmode: '\\mathmit{\\xi}'}, + 120586: {LaTeXMode.textmode: '\\mathmit{o}'}, + 120587: {LaTeXMode.textmode: '\\mathmit{\\pi}'}, + 120588: {LaTeXMode.textmode: '\\mathmit{\\rho}'}, + 120589: {LaTeXMode.textmode: '\\mathmit{\\varsigma}'}, + 120590: {LaTeXMode.textmode: '\\mathmit{\\sigma}'}, + 120591: {LaTeXMode.textmode: '\\mathmit{\\tau}'}, + 120592: {LaTeXMode.textmode: '\\mathmit{\\upsilon}'}, + 120593: {LaTeXMode.textmode: '\\mathmit{\\phi}'}, + 120594: {LaTeXMode.textmode: '\\mathmit{\\chi}'}, + 120595: {LaTeXMode.textmode: '\\mathmit{\\psi}'}, + 120596: {LaTeXMode.textmode: '\\mathmit{\\omega}'}, + 120597: {LaTeXMode.textmode: '\\partial '}, + 120598: {LaTeXMode.textmode: '\\in'}, + 120599: {LaTeXMode.textmode: '\\mathmit{\\vartheta}'}, + 120600: {LaTeXMode.textmode: '\\mathmit{\\varkappa}'}, + 120601: {LaTeXMode.textmode: '\\mathmit{\\phi}'}, + 120602: {LaTeXMode.textmode: '\\mathmit{\\varrho}'}, + 120603: {LaTeXMode.textmode: '\\mathmit{\\varpi}'}, + 120604: {LaTeXMode.textmode: '\\mathbit{\\Alpha}'}, + 120605: {LaTeXMode.textmode: '\\mathbit{\\Beta}'}, + 120606: {LaTeXMode.textmode: '\\mathbit{\\Gamma}'}, + 120607: {LaTeXMode.textmode: '\\mathbit{\\Delta}'}, + 120608: {LaTeXMode.textmode: '\\mathbit{\\Epsilon}'}, + 120609: {LaTeXMode.textmode: '\\mathbit{\\Zeta}'}, + 120610: {LaTeXMode.textmode: '\\mathbit{\\Eta}'}, + 120611: {LaTeXMode.textmode: '\\mathbit{\\Theta}'}, + 120612: {LaTeXMode.textmode: '\\mathbit{\\Iota}'}, + 120613: {LaTeXMode.textmode: '\\mathbit{\\Kappa}'}, + 120614: {LaTeXMode.textmode: '\\mathbit{\\Lambda}'}, + 120615: {LaTeXMode.textmode: '\\mathbit{M}'}, + 120616: {LaTeXMode.textmode: '\\mathbit{N}'}, + 120617: {LaTeXMode.textmode: '\\mathbit{\\Xi}'}, + 120618: {LaTeXMode.textmode: 'O'}, + 120619: {LaTeXMode.textmode: '\\mathbit{\\Pi}'}, + 120620: {LaTeXMode.textmode: '\\mathbit{\\Rho}'}, + 120621: {LaTeXMode.textmode: '\\mathbit{O}'}, + 120622: {LaTeXMode.textmode: '\\mathbit{\\Sigma}'}, + 120623: {LaTeXMode.textmode: '\\mathbit{\\Tau}'}, + 120624: {LaTeXMode.textmode: '\\mathbit{\\Upsilon}'}, + 120625: {LaTeXMode.textmode: '\\mathbit{\\Phi}'}, + 120626: {LaTeXMode.textmode: '\\mathbit{\\Chi}'}, + 120627: {LaTeXMode.textmode: '\\mathbit{\\Psi}'}, + 120628: {LaTeXMode.textmode: '\\mathbit{\\Omega}'}, + 120629: {LaTeXMode.textmode: '\\mathbit{\\nabla}'}, + 120630: {LaTeXMode.textmode: '\\mathbit{\\alpha}'}, + 120631: {LaTeXMode.textmode: '\\mathbit{\\beta}'}, + 120632: {LaTeXMode.textmode: '\\mathbit{\\gamma}'}, + 120633: {LaTeXMode.textmode: '\\mathbit{\\delta}'}, + 120634: {LaTeXMode.textmode: '\\mathbit{\\epsilon}'}, + 120635: {LaTeXMode.textmode: '\\mathbit{\\zeta}'}, + 120636: {LaTeXMode.textmode: '\\mathbit{\\eta}'}, + 120637: {LaTeXMode.textmode: '\\mathbit{\\theta}'}, + 120638: {LaTeXMode.textmode: '\\mathbit{\\iota}'}, + 120639: {LaTeXMode.textmode: '\\mathbit{\\kappa}'}, + 120640: {LaTeXMode.textmode: '\\mathbit{\\lambda}'}, + 120641: {LaTeXMode.textmode: '\\mathbit{\\mu}'}, + 120642: {LaTeXMode.textmode: '\\mathbit{\\nu}'}, + 120643: {LaTeXMode.textmode: '\\mathbit{\\xi}'}, + 120644: {LaTeXMode.textmode: '\\mathbit{o}'}, + 120645: {LaTeXMode.textmode: '\\mathbit{\\pi}'}, + 120646: {LaTeXMode.textmode: '\\mathbit{\\rho}'}, + 120647: {LaTeXMode.textmode: '\\mathbit{\\varsigma}'}, + 120648: {LaTeXMode.textmode: '\\mathbit{\\sigma}'}, + 120649: {LaTeXMode.textmode: '\\mathbit{\\tau}'}, + 120650: {LaTeXMode.textmode: '\\mathbit{\\upsilon}'}, + 120651: {LaTeXMode.textmode: '\\mathbit{\\phi}'}, + 120652: {LaTeXMode.textmode: '\\mathbit{\\chi}'}, + 120653: {LaTeXMode.textmode: '\\mathbit{\\psi}'}, + 120654: {LaTeXMode.textmode: '\\mathbit{\\omega}'}, + 120655: {LaTeXMode.textmode: '\\partial '}, + 120656: {LaTeXMode.textmode: '\\in'}, + 120657: {LaTeXMode.textmode: '\\mathbit{\\vartheta}'}, + 120658: {LaTeXMode.textmode: '\\mathbit{\\varkappa}'}, + 120659: {LaTeXMode.textmode: '\\mathbit{\\phi}'}, + 120660: {LaTeXMode.textmode: '\\mathbit{\\varrho}'}, + 120661: {LaTeXMode.textmode: '\\mathbit{\\varpi}'}, + 120662: {LaTeXMode.textmode: '\\mathsfbf{\\Alpha}'}, + 120663: {LaTeXMode.textmode: '\\mathsfbf{\\Beta}'}, + 120664: {LaTeXMode.textmode: '\\mathsfbf{\\Gamma}'}, + 120665: {LaTeXMode.textmode: '\\mathsfbf{\\Delta}'}, + 120666: {LaTeXMode.textmode: '\\mathsfbf{\\Epsilon}'}, + 120667: {LaTeXMode.textmode: '\\mathsfbf{\\Zeta}'}, + 120668: {LaTeXMode.textmode: '\\mathsfbf{\\Eta}'}, + 120669: {LaTeXMode.textmode: '\\mathsfbf{\\Theta}'}, + 120670: {LaTeXMode.textmode: '\\mathsfbf{\\Iota}'}, + 120671: {LaTeXMode.textmode: '\\mathsfbf{\\Kappa}'}, + 120672: {LaTeXMode.textmode: '\\mathsfbf{\\Lambda}'}, + 120673: {LaTeXMode.textmode: '\\mathsfbf{M}'}, + 120674: {LaTeXMode.textmode: '\\mathsfbf{N}'}, + 120675: {LaTeXMode.textmode: '\\mathsfbf{\\Xi}'}, + 120676: {LaTeXMode.textmode: 'O'}, + 120677: {LaTeXMode.textmode: '\\mathsfbf{\\Pi}'}, + 120678: {LaTeXMode.textmode: '\\mathsfbf{\\Rho}'}, + 120679: {LaTeXMode.textmode: '\\mathsfbf{\\vartheta}'}, + 120680: {LaTeXMode.textmode: '\\mathsfbf{\\Sigma}'}, + 120681: {LaTeXMode.textmode: '\\mathsfbf{\\Tau}'}, + 120682: {LaTeXMode.textmode: '\\mathsfbf{\\Upsilon}'}, + 120683: {LaTeXMode.textmode: '\\mathsfbf{\\Phi}'}, + 120684: {LaTeXMode.textmode: '\\mathsfbf{\\Chi}'}, + 120685: {LaTeXMode.textmode: '\\mathsfbf{\\Psi}'}, + 120686: {LaTeXMode.textmode: '\\mathsfbf{\\Omega}'}, + 120687: {LaTeXMode.textmode: '\\mathsfbf{\\nabla}'}, + 120688: {LaTeXMode.textmode: '\\mathsfbf{\\alpha}'}, + 120689: {LaTeXMode.textmode: '\\mathsfbf{\\beta}'}, + 120690: {LaTeXMode.textmode: '\\mathsfbf{\\gamma}'}, + 120691: {LaTeXMode.textmode: '\\mathsfbf{\\delta}'}, + 120692: {LaTeXMode.textmode: '\\mathsfbf{\\epsilon}'}, + 120693: {LaTeXMode.textmode: '\\mathsfbf{\\zeta}'}, + 120694: {LaTeXMode.textmode: '\\mathsfbf{\\eta}'}, + 120695: {LaTeXMode.textmode: '\\mathsfbf{\\theta}'}, + 120696: {LaTeXMode.textmode: '\\mathsfbf{\\iota}'}, + 120697: {LaTeXMode.textmode: '\\mathsfbf{\\kappa}'}, + 120698: {LaTeXMode.textmode: '\\mathsfbf{\\lambda}'}, + 120699: {LaTeXMode.textmode: '\\mathsfbf{\\mu}'}, + 120700: {LaTeXMode.textmode: '\\mathsfbf{\\nu}'}, + 120701: {LaTeXMode.textmode: '\\mathsfbf{\\xi}'}, + 120702: {LaTeXMode.textmode: '\\mathsfbf{o}'}, + 120703: {LaTeXMode.textmode: '\\mathsfbf{\\pi}'}, + 120704: {LaTeXMode.textmode: '\\mathsfbf{\\rho}'}, + 120705: {LaTeXMode.textmode: '\\mathsfbf{\\varsigma}'}, + 120706: {LaTeXMode.textmode: '\\mathsfbf{\\sigma}'}, + 120707: {LaTeXMode.textmode: '\\mathsfbf{\\tau}'}, + 120708: {LaTeXMode.textmode: '\\mathsfbf{\\upsilon}'}, + 120709: {LaTeXMode.textmode: '\\mathsfbf{\\phi}'}, + 120710: {LaTeXMode.textmode: '\\mathsfbf{\\chi}'}, + 120711: {LaTeXMode.textmode: '\\mathsfbf{\\psi}'}, + 120712: {LaTeXMode.textmode: '\\mathsfbf{\\omega}'}, + 120713: {LaTeXMode.textmode: '\\partial '}, + 120714: {LaTeXMode.textmode: '\\mathsfbf{\\varepsilon}'}, + 120715: {LaTeXMode.textmode: '\\mathsfbf{\\vartheta}'}, + 120716: {LaTeXMode.textmode: '\\mathsfbf{\\varkappa}'}, + 120717: {LaTeXMode.textmode: '\\mathsfbf{\\phi}'}, + 120718: {LaTeXMode.textmode: '\\mathsfbf{\\varrho}'}, + 120719: {LaTeXMode.textmode: '\\mathsfbf{\\varpi}'}, + 120720: {LaTeXMode.textmode: '\\mathsfbfsl{\\Alpha}'}, + 120721: {LaTeXMode.textmode: '\\mathsfbfsl{\\Beta}'}, + 120722: {LaTeXMode.textmode: '\\mathsfbfsl{\\Gamma}'}, + 120723: {LaTeXMode.textmode: '\\mathsfbfsl{\\Delta}'}, + 120724: {LaTeXMode.textmode: '\\mathsfbfsl{\\Epsilon}'}, + 120725: {LaTeXMode.textmode: '\\mathsfbfsl{\\Zeta}'}, + 120726: {LaTeXMode.textmode: '\\mathsfbfsl{\\Eta}'}, + 120727: {LaTeXMode.textmode: '\\mathsfbfsl{\\vartheta}'}, + 120728: {LaTeXMode.textmode: '\\mathsfbfsl{\\Iota}'}, + 120729: {LaTeXMode.textmode: '\\mathsfbfsl{\\Kappa}'}, + 120730: {LaTeXMode.textmode: '\\mathsfbfsl{\\Lambda}'}, + 120731: {LaTeXMode.textmode: '\\mathsfbfsl{M}'}, + 120732: {LaTeXMode.textmode: '\\mathsfbfsl{N}'}, + 120733: {LaTeXMode.textmode: '\\mathsfbfsl{\\Xi}'}, + 120734: {LaTeXMode.textmode: 'O'}, + 120735: {LaTeXMode.textmode: '\\mathsfbfsl{\\Pi}'}, + 120736: {LaTeXMode.textmode: '\\mathsfbfsl{\\Rho}'}, + 120737: {LaTeXMode.textmode: '\\mathsfbfsl{\\vartheta}'}, + 120738: {LaTeXMode.textmode: '\\mathsfbfsl{\\Sigma}'}, + 120739: {LaTeXMode.textmode: '\\mathsfbfsl{\\Tau}'}, + 120740: {LaTeXMode.textmode: '\\mathsfbfsl{\\Upsilon}'}, + 120741: {LaTeXMode.textmode: '\\mathsfbfsl{\\Phi}'}, + 120742: {LaTeXMode.textmode: '\\mathsfbfsl{\\Chi}'}, + 120743: {LaTeXMode.textmode: '\\mathsfbfsl{\\Psi}'}, + 120744: {LaTeXMode.textmode: '\\mathsfbfsl{\\Omega}'}, + 120745: {LaTeXMode.textmode: '\\mathsfbfsl{\\nabla}'}, + 120746: {LaTeXMode.textmode: '\\mathsfbfsl{\\alpha}'}, + 120747: {LaTeXMode.textmode: '\\mathsfbfsl{\\beta}'}, + 120748: {LaTeXMode.textmode: '\\mathsfbfsl{\\gamma}'}, + 120749: {LaTeXMode.textmode: '\\mathsfbfsl{\\delta}'}, + 120750: {LaTeXMode.textmode: '\\mathsfbfsl{\\epsilon}'}, + 120751: {LaTeXMode.textmode: '\\mathsfbfsl{\\zeta}'}, + 120752: {LaTeXMode.textmode: '\\mathsfbfsl{\\eta}'}, + 120753: {LaTeXMode.textmode: '\\mathsfbfsl{\\vartheta}'}, + 120754: {LaTeXMode.textmode: '\\mathsfbfsl{\\iota}'}, + 120755: {LaTeXMode.textmode: '\\mathsfbfsl{\\kappa}'}, + 120756: {LaTeXMode.textmode: '\\mathsfbfsl{\\lambda}'}, + 120757: {LaTeXMode.textmode: '\\mathsfbfsl{\\mu}'}, + 120758: {LaTeXMode.textmode: '\\mathsfbfsl{\\nu}'}, + 120759: {LaTeXMode.textmode: '\\mathsfbfsl{\\xi}'}, + 120760: {LaTeXMode.textmode: '\\mathsfbfsl{o}'}, + 120761: {LaTeXMode.textmode: '\\mathsfbfsl{\\pi}'}, + 120762: {LaTeXMode.textmode: '\\mathsfbfsl{\\rho}'}, + 120763: {LaTeXMode.textmode: '\\mathsfbfsl{\\varsigma}'}, + 120764: {LaTeXMode.textmode: '\\mathsfbfsl{\\sigma}'}, + 120765: {LaTeXMode.textmode: '\\mathsfbfsl{\\tau}'}, + 120766: {LaTeXMode.textmode: '\\mathsfbfsl{\\upsilon}'}, + 120767: {LaTeXMode.textmode: '\\mathsfbfsl{\\phi}'}, + 120768: {LaTeXMode.textmode: '\\mathsfbfsl{\\chi}'}, + 120769: {LaTeXMode.textmode: '\\mathsfbfsl{\\psi}'}, + 120770: {LaTeXMode.textmode: '\\mathsfbfsl{\\omega}'}, + 120771: {LaTeXMode.textmode: '\\partial '}, + 120772: {LaTeXMode.textmode: '\\in'}, + 120773: {LaTeXMode.textmode: '\\mathsfbfsl{\\vartheta}'}, + 120774: {LaTeXMode.textmode: '\\mathsfbfsl{\\varkappa}'}, + 120775: {LaTeXMode.textmode: '\\mathsfbfsl{\\phi}'}, + 120776: {LaTeXMode.textmode: '\\mathsfbfsl{\\varrho}'}, + 120777: {LaTeXMode.textmode: '\\mathsfbfsl{\\varpi}'}, + 120782: {LaTeXMode.textmode: '\\mathbf{0}'}, + 120783: {LaTeXMode.textmode: '\\mathbf{1}'}, + 120784: {LaTeXMode.textmode: '\\mathbf{2}'}, + 120785: {LaTeXMode.textmode: '\\mathbf{3}'}, + 120786: {LaTeXMode.textmode: '\\mathbf{4}'}, + 120787: {LaTeXMode.textmode: '\\mathbf{5}'}, + 120788: {LaTeXMode.textmode: '\\mathbf{6}'}, + 120789: {LaTeXMode.textmode: '\\mathbf{7}'}, + 120790: {LaTeXMode.textmode: '\\mathbf{8}'}, + 120791: {LaTeXMode.textmode: '\\mathbf{9}'}, + 120792: {LaTeXMode.textmode: '\\mathbb{0}'}, + 120793: {LaTeXMode.textmode: '\\mathbb{1}'}, + 120794: {LaTeXMode.textmode: '\\mathbb{2}'}, + 120795: {LaTeXMode.textmode: '\\mathbb{3}'}, + 120796: {LaTeXMode.textmode: '\\mathbb{4}'}, + 120797: {LaTeXMode.textmode: '\\mathbb{5}'}, + 120798: {LaTeXMode.textmode: '\\mathbb{6}'}, + 120799: {LaTeXMode.textmode: '\\mathbb{7}'}, + 120800: {LaTeXMode.textmode: '\\mathbb{8}'}, + 120801: {LaTeXMode.textmode: '\\mathbb{9}'}, + 120802: {LaTeXMode.textmode: '\\mathsf{0}'}, + 120803: {LaTeXMode.textmode: '\\mathsf{1}'}, + 120804: {LaTeXMode.textmode: '\\mathsf{2}'}, + 120805: {LaTeXMode.textmode: '\\mathsf{3}'}, + 120806: {LaTeXMode.textmode: '\\mathsf{4}'}, + 120807: {LaTeXMode.textmode: '\\mathsf{5}'}, + 120808: {LaTeXMode.textmode: '\\mathsf{6}'}, + 120809: {LaTeXMode.textmode: '\\mathsf{7}'}, + 120810: {LaTeXMode.textmode: '\\mathsf{8}'}, + 120811: {LaTeXMode.textmode: '\\mathsf{9}'}, + 120812: {LaTeXMode.textmode: '\\mathsfbf{0}'}, + 120813: {LaTeXMode.textmode: '\\mathsfbf{1}'}, + 120814: {LaTeXMode.textmode: '\\mathsfbf{2}'}, + 120815: {LaTeXMode.textmode: '\\mathsfbf{3}'}, + 120816: {LaTeXMode.textmode: '\\mathsfbf{4}'}, + 120817: {LaTeXMode.textmode: '\\mathsfbf{5}'}, + 120818: {LaTeXMode.textmode: '\\mathsfbf{6}'}, + 120819: {LaTeXMode.textmode: '\\mathsfbf{7}'}, + 120820: {LaTeXMode.textmode: '\\mathsfbf{8}'}, + 120821: {LaTeXMode.textmode: '\\mathsfbf{9}'}, + 120822: {LaTeXMode.textmode: '\\mathtt{0}'}, + 120823: {LaTeXMode.textmode: '\\mathtt{1}'}, + 120824: {LaTeXMode.textmode: '\\mathtt{2}'}, + 120825: {LaTeXMode.textmode: '\\mathtt{3}'}, + 120826: {LaTeXMode.textmode: '\\mathtt{4}'}, + 120827: {LaTeXMode.textmode: '\\mathtt{5}'}, + 120828: {LaTeXMode.textmode: '\\mathtt{6}'}, + 120829: {LaTeXMode.textmode: '\\mathtt{7}'}, + 120830: {LaTeXMode.textmode: '\\mathtt{8}'}, + 120831: {LaTeXMode.textmode: '\\mathtt{9}'}, + } + diff --git a/support/gladtex/manpage.md b/support/gladtex/manpage.md new file mode 100644 index 0000000000..f4127db1f7 --- /dev/null +++ b/support/gladtex/manpage.md @@ -0,0 +1,249 @@ +% GLADTEX(1) +% Sebastian Humenda +% 8th of September 2018 + +# NAME + +**GladTeX** - generate HTML with LaTeX formulas embedded as images + +# SYNOPSIS + +**gladtex** [OPTIONS] <INPUT FILE NAME> + + +# DESCRIPTION + +**GladTeX** is a formula preprocessor for HTML files. It recognizes a special tag +(`<eq>...</eq>`) marking formulas for conversion. The converted vector images +are integrated into the output HTML document. +This eases the process of creating HTML +documents (or web sites) containing formulas.\ +The generated images are saved in a cache to not render the same image over +and over again. This speeds up the process when formulas occur multiple times or +when a document is extended gradually. + +The LaTeX formulas are preserved in the alt attribute of the embedded images, +hence screen reader users benefit from an accessible HTML version of the +document. + +Furthermore it can be used with Pandoc to convert Markdown documents and other +formats with LaTeX formulas to HTML, EPUB and in fact to any HTML-based format, +see the option `-P`. + +See [FILE FORMAT](#file-format) for an explanation of the file format and +[EXAMPLES](#examples) for examples on how to use GladTeX on its own or with +Pandoc. + +# OPTIONS + +**INPUT FILE NAME** +: Input .htex file with LaTeX formulas (if omitted or -, stdin will be read). + +**-h** **--help** +: Show this help message and exit. + +**-a** +: Save text alternatives for images which are too long for the alt attribute + into a single separate file and link images to it. + +**-b** _BACKGROUND_COLOR_ +: Set background color for resulting images (default transparent). GladTeX + understands colors as provided by the `dvips` option of the xcolor LaTeX + package. Alternatively, a 6-digit hexadecimal value can be provided (as used + e.g. in HTML/CSS). + +**-c** _`FOREGROUND_COLOR`_ +: Set foreground color for resulting images. See the option above for a more +in-depth explanation. + +**-d** _DIRECTORY_ +: Directory in which to store the generated images in (relative path).\ + The given path is interpreted relatively to the input file. For instance,: + + gladtex -d img dir/file.htex + + will create a `dir/img` directory and link accordingly in `x/file.htex`. + +**-e** _`LATEX_MATHS_ENV`_ +: Set custom maths environment to surround the formula (e.g. flalign). + +**-E** _ENCODING_ +: Overwrite encoding to use (default UTF-8). + +**-f** _FONTSIZE_ +: Overwrite the default font size of 12pt. 12pt is the default in most + browsers and hence changing this might lead to less-portable documents. + +**-i** _CLASS_ +: CSS class to assign to inline math (default: 'inlinemath'). + +**-K** +: keep LaTeX file(s) when converting formulas + + By default, the generated LaTeX document, containing the formula to be + converted, are removed after the conversion (no matter whether it was + successful or not). If it wasn't successful, it is sometimes helpful to look + at the complete document. This option will keep the file. + +**-l** _CLASS_ +: CSS class to assign to block-level math (default: 'displaymath'). + +**-n** +: Purge unreadable caches along with all eqn*.png files. + + Caches can be unreadable if the used GladTeX version is incompatible. If + this option is unset, GladTeX will simply fail when the cache is unreadable. + +**-m** +: Print error output in machine-readable format (less concise, better parseable). + + Each line will start with a key, followed by a colon, followed by the value, + i.e. `line: 5`. + +**-o** _FILENAME_ +: Set output file name. '-' will print text to stdout. Bydefault, input file + name is used and the `.htex` extension is replaced by `.html`. + +**-p** _`LATEX_STATEMENT`_ +: Add given LaTeX code to preamble of document. That'll affect the conversion + of every image. + +**-P** +: Act as a pandoc filter. In this mode, input is expected to be a Pandoc JSON + AST and the output will be a modified AST, with all formulas replaced + through HTML image tags. It makes sense to use `-` as the input file for + this option. + +**--png** +: Switch from SVG to PNG as image output. This image has several known issues, + one of them being that images won't resize when zooming into the document. + It is also harder to work with for visually impaired users. + +**-r** _DPI_ +: Set resolution (size of images) to 'dpi' (115 by default). This is only + available with the `--png` option. Also see the `-f` option. + +**-R** +: Replace non-ascii (unicode) characters by LaTeX commands. + + GladTeX can automatically detect non-ascii characters in formulas and + replace them through their appropriate LaTeX commands. In the alt attribute + of the resulting image, alphabetical characters won't be replaced. That + means that the alt text from the image is not exactly the same than the + code used for generating the image, but it is far more readable. + + For instance, the formula \$\\text{für alle} a\$, would be compiled as + \$\\text{f\\ddot{u}r alle} a\$ and displayed as "\\text{für alle} a" in the alt + attribute. + + +**-u** _URL_ +: Base URL to image files (relative links are default). + +# FILE FORMAT + +A .htex file is essentially a HTML file containing LaTeX formulas. The formulas +have to be surrounded by `<eq>` and `</eq>`. + +By default, formulas are rendered as inline maths, so they are squeezed to the +height of the line. It is possible to render a formula as display maths by +setting the env attribute to displaymath, i.e. `<eq env="displaymath">...</eq>`. + +# ENVIRONMENT VARIABLES + +GladTeX can be customised by environment variables: + +`DEBUG` +: If this is set to 1, a full Python traceback, instead of a human-readable + error message, will be displayed. +`GLADTEX_ARGS`: +: When this environment variable is set, GladTeX switches into + **pandoc filter** mode: input is read from standard input, output written to + standard output and the `-P` switch is assumed. The contents of this + variable parsed as command-line switches. + See an example in [Output As EPUB]#output-asepub). + +# EXAMPLES + +## Sample HTEX document + +A sample HTEX document could look like this: + +~~~~ +<html><head><!-- meta information like charset --></head> +<body> +<h1>Some text</h1> +<p>Circumference of a circle: <eq>u = \pi\cdot d</eq><p> +<p>A useful matrix: <eq env="displaymath">\begin{pmatrix} +1 &2 &3 &4\\ +5 &6 &7 &8\\ +9 &10&11&12 +\end{pmatrix}</eq></p> +</body></html> +~~~~ + +This can be converted using + + gladtex file.htex + +and the result will be a HTML document called `file.html` along with two files +`eqn0000.png` and `eqn0001.png` in the same directory. + +## Markdown To HTML + +GladTeX can be used together with Pandoc. That can be handy to create an online +version of a scientific paper written in Markdown. The MarkDown document would +look like this: + +~~~~ +Some text +========= + +Circumference of a circle: $u = \pi\cdot d$ + +A useful matrix: $$\begin{pmatrix} +1 &2 &3 &4\\ +5 &6 &7 &8\\ +9 &10&11&12 \end{pmatrix}$$ +~~~~ + +The conversion is as easy as typing on the command-line: + + pandoc -s -t html --gladtex file.md | gladtex -o file.html + +## Output as EPUB + +It is beyond of the scope of this document to introduce Pandoc, but with any +input format, converting to EPUB with GladTeX replacing the images is as easy +as: + + pandoc -t json FILE.ext | gladtex -d img -P - | pandoc -f json -o book.epub + +Capitalised parameters should be replaced. This can be used with Markdown as +input format, see previous section. + +If you want to call Pandoc as a filter without the pipes, you can use the +environment variable `GLADTEX_ARGS`: + + GLADTEX_ARGS='-d img' pandoc -o BOOK.EPUB -F gladtex FILE.ext + + +# KNOWN LIMITATIONS + +LaTeX2E is ***not*** unicode aware. if you have any unicode (more precisely, +non-ascii characters) signs in your documents, you have the choice to do one of +the following: + +1. Look up the symbol in one of the many LaTeX formula listings and replace the + symbol with the appropriate command. +2. Use the `-r` switch to let GladTeX replace the umlauts for you. + +PLEASE NOTE: It is impossible to use GladTeX with LuaLaTeX. At the time of writing, dvipng +does not support the extended font features of the lualatex engine. + + +# PROJECT HOME + +The project home is at <http://humenda.github.io/GladTeX>. The source can be +found at <https://github.com/humenda/gladtex>. + diff --git a/support/gladtex/runtests b/support/gladtex/runtests new file mode 100755 index 0000000000..521d5b3a33 --- /dev/null +++ b/support/gladtex/runtests @@ -0,0 +1,11 @@ +#!/bin/sh +# change above to python, if your system doesn't have the 3 suffix for python 3 +set -e +PYTHON=python +# check whether a program called "python3" exists, else use "python" +if command -v python3 >/dev/null 2>&1 +then + PYTHON=python3 +fi + +$PYTHON -m unittest discover tests diff --git a/support/gladtex/setup.py b/support/gladtex/setup.py new file mode 100644 index 0000000000..9f17c165ec --- /dev/null +++ b/support/gladtex/setup.py @@ -0,0 +1,99 @@ +import distutils.command.install_scripts, distutils.command.build, distutils.command.clean +from distutils.core import setup +import os +import shutil +import sys +#pylint: disable=unused-import +try: + import py2exe # only works on windows +except ImportError: + pass +from gleetex import VERSION + +class ScriptInstaller(distutils.command.install_scripts.install_scripts): + """Custom script installer. Stript .py extension if not on Windows.""" + def run(self): + distutils.command.install_scripts.install_scripts.run(self) + for script in self.get_outputs(): + if script.endswith(".py") and not ('wind' in sys.platform or 'win32' + in sys.platform): + # strip file ending (if not on windows) to make it executable as + # a command + shutil.move(script, script[:-3]) + +#pylint: disable=attribute-defined-outside-init +class CleanCommand(distutils.command.clean.clean): + description = "clean all build files, including __pycache__ and others" + user_options = [] + def initialize_options(self): + self.cwd = None + + def finalize_options(self): + self.cwd = os.getcwd() + + def run(self): + assert os.getcwd() == self.cwd, 'Must be in package root: %s' % self.cwd + for directory in ['build', '__pycache__', 'dist', + os.path.join('gleetex', '__pycache')]: + if os.path.exists(directory): + shutil.rmtree(directory) + if os.path.exists('gladtex.1'): + os.remove('gladtex.1') + +class CustomBuild(distutils.command.build.build): + """Also build manpage to build/gladtex.1; it is not installed + automatically.""" + def initialize_options(self): + self.cwd = None + super().initialize_options() + + def finalize_options(self): + self.cwd = os.getcwd() + super().finalize_options() + + def run(self): + if not self.cwd: + if not 'manpage.md' in os.listdir('.'): + print("setup.py must be run from the source root") + sys.exit(91) + else: + assert os.getcwd() == self.cwd, 'Must be in package root: %s' % self.cwd + super().run() + if shutil.which('pandoc'): # only build man page, if pandoc present + self.build_manpage('manpage.md', os.path.join('build', 'gladtex.1')) + else: + print("w: pandoc not found, skipping man page conversion.") + + def build_manpage(self, input_fn, output_fn): + """Convert `input_fn` from markdown into manpage format and save it to + `output_fn`. Pandoc is required.""" + import subprocess + try: + cmd = ['pandoc', input_fn, '-s', '-t', 'man', '-o', output_fn] + proc = subprocess.Popen(cmd) + ret = proc.wait() + if ret: + raise subprocess.SubprocessError("Exit status %d when running '%s'" % (ret, + ' '.join(cmd))) + except FileNotFoundError: + sys.stderr.write("Pandoc was not found on the system, skipping man " + + "page creation.") + + +setup(name='GladTeX', + version=VERSION, + description='generate html with LaTeX equations embedded as images', + author='Sebastian Humenda', + author_email='shumenda |at| gmx |dot| de', + url='https://humenda.github.io/GladTeX', + packages=['gleetex'], + console=['gladtex.py'], # Windows-only option, use python instead of pythonw + setup_requires=['py2app'], # require py2app on Mac OS + scripts=['gladtex.py'], + license = "LGPL3.0", + cmdclass = {"install_scripts": ScriptInstaller, + 'clean': CleanCommand, + 'build': CustomBuild} + ) + + diff --git a/support/gladtex/tests/test_cachedconverter.py b/support/gladtex/tests/test_cachedconverter.py new file mode 100644 index 0000000000..b1c3bb786c --- /dev/null +++ b/support/gladtex/tests/test_cachedconverter.py @@ -0,0 +1,143 @@ +#pylint: disable=too-many-public-methods,import-error,too-few-public-methods,missing-docstring,unused-variable +import os +import shutil +import tempfile +import unittest +from unittest.mock import patch +from gleetex import cachedconverter, image +from gleetex.caching import JsonParserException +from gleetex.image import remove_all + +def get_number_of_files(path): + return len(os.listdir(path)) + +def mk_eqn(eqn, count=0, pos=(1,1)): + """Create formula. Each formula must look like this: + (eqn, pos, path, dsp, count) for self._convert_concurrently, this is a + shorthand with mocking a few values.""" + return (pos, False, eqn) + +def write(path, content='dummy'): + with open(path, 'w', encoding='utf-8') as f: + f.write(str(content)) + +class Tex2imgMock(): + """Could use a proper mock, but this one allows a bit more tricking.""" + def __init__(self, fmt): + self.__format = fmt + self.set_dpi = self.set_transparency = self.set_foreground_color \ + = self.set_background_color = lambda x: None # do nothing + + def create_dvi(self, dvi_fn): + with open(dvi_fn, 'w') as f: + f.write('dummy') + + def create_image(self, dvi_fn): + if os.path.exists(dvi_fn): + os.remove(dvi_fn) + write(os.path.splitext(dvi_fn)[0] + '.' + self.__format.value) + + def convert(self, tx, basename): + write(basename + '.tex', tx) + dvi = basename + '.dvi' + self.create_dvi(dvi) + self.create_image(dvi) + remove_all(dvi, basename + '.tex', basename + '.log', basename + '.aux') + return {'depth': 9, 'height': 8, 'width': 7} + + def parse_log(self, _logdata): + return {} + + +class TestCachedConverter(unittest.TestCase): + #pylint: disable=protected-access + def setUp(self): + self.original_directory = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + os.chdir(self.tmpdir) + + + #pylint: disable=protected-access + def tearDown(self): + # restore static reference to converter + cachedconverter.CachedConverter._converter = image.Tex2img + os.chdir(self.original_directory) + shutil.rmtree(self.tmpdir, ignore_errors=True) + + + + @patch('gleetex.image.Tex2img', Tex2imgMock) + def test_that_subdirectory_is_created(self): + c = cachedconverter.CachedConverter('subdirectory') + formula = ({}, True, '\\textbf{FOO!}') + c.convert_all([formula]) + # one directory exists + self.assertEqual(get_number_of_files('.'), 1, + "Found the following files, expected only 'subdirectory': " + \ + ', '.join(os.listdir('.'))) + # subdirectory contains 1 image and a cache + self.assertEqual(get_number_of_files('subdirectory'), 2, "expected two"+\ + " files, found instead " + repr(os.listdir('subdirectory'))) + + def test_that_unknown_options_trigger_exception(self): + c = cachedconverter.CachedConverter('subdirectory') + self.assertRaises(ValueError, c.set_option, 'cxzbiucxzbiuxzb', 'muh') + + def test_that_invalid_caches_trigger_error_by_default(self): + with open('gladtex.cache', 'w') as f: + f.write('invalid cache') + with self.assertRaises(JsonParserException): + c = cachedconverter.CachedConverter('') + + @patch('gleetex.image.Tex2img', Tex2imgMock) + def test_that_invalid_caches_get_removed_if_specified(self): + formulas = [mk_eqn('tau')] + with open('gladtex.cache', 'w') as f: + f.write('invalid cache') + c = cachedconverter.CachedConverter('.', keep_old_cache=False) + c.convert_all(formulas) + # cache got overridden + with open('gladtex.cache') as f: + self.assertFalse('invalid' in f.read()) + + @patch('gleetex.image.Tex2img', Tex2imgMock) + def test_that_converted_formulas_are_cached(self): + formulas = [mk_eqn('\\tau')] + c = cachedconverter.CachedConverter('.') + c.convert_all(formulas) + self.assertTrue(c.get_data_for('\\tau', False)) + + + @patch('gleetex.image.Tex2img', Tex2imgMock) + def test_that_file_names_are_correctly_picked(self): + formulas = [mk_eqn('\\tau')] + write('eqn000.svg') + write('eqn001.svg') + c = cachedconverter.CachedConverter('') + to_convert = c._get_formulas_to_convert(formulas) + self.assertTrue(len(to_convert), 1) + self.assertEqual(to_convert[0][2], 'eqn002.svg') + + @patch('gleetex.image.Tex2img', Tex2imgMock) + def test_that_all_converted_formulas_are_in_cache_and_meta_info_correct(self): + formulas = [mk_eqn('a_{%d}' % i, pos=(i,i), count=i) for i in range(4)] + c = cachedconverter.CachedConverter('.') + c.convert_all(formulas) + # expect all formulas and a gladtex cache to exist + self.assertEqual(get_number_of_files('.'), len(formulas)+1, + "present files:\n" + ', '.join(os.listdir('.'))) + for pos, dsp, formula in formulas: + data = c.get_data_for(formula, False) + self.assertEqual(data['pos'], {'depth': 9, 'height': 8, 'width': 7}, + "expected the pos as defined in the dummy class") + + @patch('gleetex.image.Tex2img', Tex2imgMock) + def test_that_inline_math_and_display_math_results_in_different_formulas(self): + # two formulas, second is displaymath + formula = r'\sum_{i=0}^n x_i' + formulas = [((1,1), False, formula), ((3,1), True, formula)] + c = cachedconverter.CachedConverter('.') + c.convert_all(formulas) + # expect all formulas and a gladtex cache to exist + self.assertEqual(get_number_of_files('.'), len(formulas)+1, + "present files:\n%s" % ', '.join(os.listdir('.'))) diff --git a/support/gladtex/tests/test_caching.py b/support/gladtex/tests/test_caching.py new file mode 100644 index 0000000000..dd3aebac77 --- /dev/null +++ b/support/gladtex/tests/test_caching.py @@ -0,0 +1,149 @@ +#pylint: disable=too-many-public-methods,import-error,too-few-public-methods,missing-docstring,unused-variable +import os +import shutil +import tempfile +import unittest +from gleetex import caching + +def write(path, content): + with open(path, 'w', encoding='utf-8') as f: + f.write(content) + +class test_caching(unittest.TestCase): + def setUp(self): + self.pos = {'height' : 8, 'depth' : 2, 'width' : 666} + self.original_directory = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + os.chdir(self.tmpdir) + + def tearDown(self): + os.chdir(self.original_directory) + shutil.rmtree(self.tmpdir, ignore_errors=True) + + def test_differently_spaced_formulas_are_the_same(self): + form1 = r'\tau \pi' + form2 = '\tau\\pi' + self.assertTrue(caching.normalize_formula(form1), + caching.normalize_formula(form2)) + + def test_trailing_and_leading_spaces_and_tabs_are_no_problem(self): + u = caching.normalize_formula + form1 = ' hi' + form2 = 'hi ' + form3 = '\thi' + self.assertEqual(u(form1), u(form2)) + self.assertEqual(u(form1), u(form3)) + + def test_that_empty_braces_are_ignored(self): + u = caching.normalize_formula + form1 = r'\sin{}x' + form2 = r'\sin x' + form3 = r'\sin{} x' + self.assertEqual(u(form1), u(form2)) + self.assertEqual(u(form1), u(form3)) + self.assertEqual(u(form2), u(form3)) + + def test_empty_cache_works_fine(self): + write('foo.png', 'muha') + c = caching.ImageCache('file.png') + formula = r"f(x) = \ln(x)" + c.add_formula(formula, self.pos, 'foo.png') + self.assertTrue(c.contains(formula, False)) + + def test_that_invalid_cach_entries_are_detected(self): + # entry is invalid if file doesn't exist + c = caching.ImageCache() + formula = r"f(x) = \ln(x)" + self.assertRaises(OSError, c.add_formula, formula, self.pos, 'file.png') + + def test_that_correct_pos_and_path_are_returned_after_writing_the_cache_back(self): + c = caching.ImageCache() + formula = r"g(x) = \ln(x)" + write('file.png', 'dummy') + c.add_formula(formula, self.pos, 'file.png', displaymath=False) + c.write() + c = caching.ImageCache() + self.assertTrue(c.contains(formula, False)) + data = c.get_data_for(formula, False) + self.assertEqual(data['pos'], self.pos) + self.assertEqual(data['path'], 'file.png') + + + def test_formulas_are_not_added_twice(self): + form1 = r'\ln(x) \neq e^x' + write('spass.png', 'binaryBinary_binary') + c = caching.ImageCache() + for i in range(1,10): + c.add_formula(form1, self.pos, 'spass.png') + self.assertEqual(len(c), 1) + + def test_that_remove_actually_removes(self): + form1 = '\\int e^x dy' + write('happyness.png', 'binaryBinary_binary') + c = caching.ImageCache() + c.add_formula(form1, self.pos, 'happyness.png') + c.remove_formula(form1, False) + self.assertEqual(len(c), 0) + + def test_removal_of_non_existing_formula_raises_exception(self): + c = caching.ImageCache() + self.assertRaises(KeyError, c.remove_formula, 'Haha!', False) + + def test_that_invalid_version_is_detected(self): + c = caching.ImageCache('gladtex.cache') + c._ImageCache__set_version('invalid.stuff') + c.write() + self.assertRaises(caching.JsonParserException, caching.ImageCache, 'gladtex.cache') + + def test_that_invalid_style_is_detected(self): + write('foo.png', "dummy") + c = caching.ImageCache('gladtex.cache') + c.add_formula('\\tau', self.pos, 'foo.png', False) + c.add_formula('\\theta', self.pos, 'foo.png', True) + self.assertRaises(ValueError, c.add_formula, '\\gamma', self.pos, 'foo.png', + 'some stuff') + + def test_that_backslash_in_path_is_replaced_through_slash(self): + c = caching.ImageCache('gladtex.cache') + os.mkdir('bilder') + write(os.path.join('bilder', 'foo.png'), str(0xdeadbeef)) + c.add_formula('\\tau', self.pos, 'bilder\\foo.png', False) + self.assertTrue('/' in c.get_data_for('\\tau', False)['path']) + + def test_that_absolute_paths_trigger_OSError(self): + c = caching.ImageCache('gladtex.cache') + write('foo.png', "dummy") + fn = os.path.abspath('foo.png') + self.assertRaises(OSError, c.add_formula, '\\tau', self.pos, + fn, False) + + def test_that_invalid_caches_are_removed_automatically_if_desired(self): + file_was_removed = lambda x: self.assertFalse(os.path.exists(x), + "expected that file %s was removed, but it still exists" % x) + write('gladtex.cache', 'some non-json rubbish') + c = caching.ImageCache('gladtex.cache', keep_old_cache=False) + file_was_removed('gladtex.cache') + # try the same in a subdirectory + os.mkdir('foo') + cache_path = os.path.join('foo', 'gladtex.cache') + eqn1_path = os.path.join('foo', 'eqn000.png') + eqn2_path = os.path.join('foo', 'eqn003.png') + write(cache_path, 'some non-json rubbish') + write(eqn1_path, 'binary') + write(eqn2_path, 'more binary') + c = caching.ImageCache(cache_path, keep_old_cache=False) + file_was_removed(cache_path) + file_was_removed(eqn1_path) + file_was_removed(eqn2_path) + + def test_that_formulas_in_cache_with_no_file_raise_key_error(self): + c = caching.ImageCache('gladtex.cache', keep_old_cache=False) + write('foo.png', 'dummy') + c.add_formula('\\tau', self.pos, 'foo.png') + c.write() + os.remove('foo.png') + c = caching.ImageCache('gladtex.cache', keep_old_cache=False) + with self.assertRaises(KeyError): + c.get_data_for('foo.png', 'False') + + diff --git a/support/gladtex/tests/test_htmlhandling.py b/support/gladtex/tests/test_htmlhandling.py new file mode 100644 index 0000000000..359ef36965 --- /dev/null +++ b/support/gladtex/tests/test_htmlhandling.py @@ -0,0 +1,404 @@ +#pylint: disable=too-many-public-methods +from functools import reduce +import os, re, shutil, tempfile +import unittest +from gleetex import htmlhandling + + +excl_filename = htmlhandling.HtmlImageFormatter.EXCLUSION_FILE_NAME + +HTML_SKELETON = '''<html><head><meta http-equiv="Content-Type" content="text/html; charset={0}" /> +</head><body>{1}</body>''' + +def read(file_name, mode='r', encoding='utf-8'): + """Read the file, return the string. Close file properly.""" + with open(file_name, mode, encoding=encoding) as handle: + return handle.read() + + +class HtmlparserTest(unittest.TestCase): + def setUp(self): + self.p = htmlhandling.EqnParser() + + def test_start_tags_are_parsed_literally(self): + self.p.feed("<p i='o'>") + self.assertEqual(self.p.get_data()[0], "<p i='o'>", + "The HTML parser should copy start tags literally.") + + def test_that_end_tags_are_copied_literally(self): + self.p.feed("</ p></P>") + self.assertEqual(''.join(self.p.get_data()), "</ p></P>") + + def test_entities_are_unchanged(self): + self.p.feed("
") + self.assertEqual(self.p.get_data()[0], '
') + + def test_charsets_are_copied(self): + self.p.feed('>→') + self.assertEqual(''.join(self.p.get_data()[0]), '>→') + + def test_without_eqn_all_blocks_are_strings(self): + self.p.feed("<html>\n<head/><body><p>42</p><h1>blah</h1></body></html>") + self.assertTrue(reduce(lambda x,y: x and isinstance(y, str), + self.p.get_data()), "all chunks have to be strings") + + def test_equation_is_detected(self): + self.p.feed('<eq>foo \\pi</eq>') + self.assertTrue(isinstance(self.p.get_data()[0], (tuple, list))) + self.assertEqual(self.p.get_data()[0][2], 'foo \\pi') + + def test_tag_followed_by_eqn_is_correctly_recognized(self): + self.p.feed('<p foo="bar"><eq>bar</eq>') + self.assertEqual(self.p.get_data()[0], '<p foo="bar">') + self.assertTrue(isinstance(self.p.get_data(), list), "second item of data must be equation data list") + + def test_document_with_tag_then_eqn_then_tag_works(self): + self.p.feed('<div style="invalid">bar</div><eq>baz</eq><sometag>') + eqn = None + # test should not depend on a specific position of equation, search for + # it + data = self.p.get_data() + for chunk in data: + if isinstance(chunk, (tuple, list)): + eqn = chunk + break + self.assertTrue(isinstance(data[0], str)) + self.assertTrue(eqn is not None, + "No equation found, must be tuple/list object.") + self.assertTrue(isinstance(data[-1], str)) + + def test_equation_is_copied_literally(self): + self.p.feed('<eq ignore="me">my\nlittle\n\\tau</eq>') + self.assertEqual(self.p.get_data()[0][2], 'my\nlittle\n\\tau') + + def test_unclosed_eqns_are_detected(self): + self.assertRaises(htmlhandling.ParseException, self.p.feed, + '<p><eq>\\endless\\formula') + + def test_nested_formulas_trigger_exception(self): + self.assertRaises(htmlhandling.ParseException, self.p.feed, + "<eq>\\pi<eq></eq></eq>") + self.assertRaises(htmlhandling.ParseException, self.p.feed, + "<eq>\\pi<eq></p></eq>") + + def test_formulas_without_displaymath_attribute_are_detected(self): + self.p.feed('<p><eq>\frac12</eq><br /><eq env="inline">bar</eq></p>') + formulas = [c for c in self.p.get_data() if isinstance(c, (tuple, list))] + self.assertEqual(len(formulas), 2) # there should be _2_ formulas + self.assertEqual(formulas[0][1], False) # no displaymath + self.assertEqual(formulas[1][1], False) # no displaymath + + def test_that_unclosed_formulas_detected(self): + self.assertRaises(htmlhandling.ParseException, self.p.feed, + "<eq>\\pi<eq></p>") + self.assertRaises(htmlhandling.ParseException, self.p.feed, + "<eq>\\pi") + + + def test_formula_contains_only_formula(self): + p = htmlhandling.EqnParser() + p.feed("<p><eq>1<i<9</eq></p>") + formula = next(e for e in p.get_data() if isinstance(e, (list, tuple))) + self.assertEqual(formula[-1], "1<i<9") + + p = htmlhandling.EqnParser() + p.feed('<p><eq env="displaymath">test</eq></p>') + formula = next(e for e in p.get_data() if isinstance(e, (list, tuple))) + self.assertEqual(formula[-1], "test") + + p = htmlhandling.EqnParser() + p.feed("<p><eq>1<i<9</eq></p>") + formula = next(e for e in p.get_data() if isinstance(e, (list, tuple))) + self.assertEqual(formula[-1], "1<i<9") + + + def test_formula_with_html_sequences_are_unescaped(self): + self.p.feed('<eq>a>b</eq>') + formula = self.p.get_data()[0] + self.assertEqual(formula[-1], "a>b") + + + def test_displaymath_is_recognized(self): + self.p.feed('<eq env="displaymath">\\sum\limits_{n=1}^{e^i} a^nl^n</eq>') + self.assertEqual(self.p.get_data()[0][1], True) # displaymath flag set + + def test_encoding_is_parsed_from_HTML4(self): + iso8859_1 = HTML_SKELETON.format('iso-8859-15', 'öäüß').encode('iso-8859-1') + self.p.feed(iso8859_1) + self.assertEqual(self.p._EqnParser__encoding, 'iso-8859-15') + + def test_encoding_is_parsed_from_HTML5(self): + document = r"""<!DOCTYPE html> + <html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> + <head><meta charset="utf-8" /> + <meta name="generator" content="pandoc" /> + </head><body><p>hi</p></body></html>""" + self.p.feed(document.encode('utf-8')) + self.assertEqual(self.p._EqnParser__encoding.lower(), 'utf-8') + + + def test_strings_can_be_passed_tO_parser_as_well(self): + # no exception - everything is working as expected + self.p.feed(HTML_SKELETON.format('utf-8', 'æø')) + +class GetPositionTest(unittest.TestCase): + def test_that_line_number_is_correct(self): + self.assertEqual(htmlhandling.get_position('jojo', 0)[0], 0) + self.assertEqual(htmlhandling.get_position('jojo', 3)[0], 0) + self.assertEqual(htmlhandling.get_position('a\njojo', 3)[0], 1) + self.assertEqual(htmlhandling.get_position('a\n\njojo', 3)[0], 2) + + def test_that_position_on_line_is_correct(self): + self.assertEqual(htmlhandling.get_position('jojo', 0)[1], 0) + self.assertEqual(htmlhandling.get_position('jojo', 3)[1], 3) + self.assertEqual(htmlhandling.get_position('a\njojo', 3)[1], 2) + self.assertEqual(htmlhandling.get_position('a\n\njojo', 3)[1], 1) + + + + +class HtmlImageTest(unittest.TestCase): + def setUp(self): + self.pos = {'depth':99, 'height' : 88, 'width' : 77} + self.original_directory = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + os.chdir(self.tmpdir) + + def tearDown(self): + os.chdir(self.original_directory) + shutil.rmtree(self.tmpdir, ignore_errors=True) + + def test_that_no_file_is_written_if_no_content(self): + with htmlhandling.HtmlImageFormatter('foo.html'): + pass + self.assertFalse(os.path.exists('foo.html') ) + + def test_file_if_written_when_content_exists(self): + with htmlhandling.HtmlImageFormatter() as img: + img.format_excluded(self.pos, '\\tau\\tau', 'foo.png') + self.assertTrue(os.path.exists(excl_filename) ) + + def test_written_file_starts_and_ends_more_or_less_properly(self): + with htmlhandling.HtmlImageFormatter('.') as img: + img.format_excluded(self.pos, '\\tau\\tau', 'foo.png') + data = read(htmlhandling.HtmlImageFormatter.EXCLUSION_FILE_NAME, 'r', encoding='utf-8') + self.assertTrue('<html' in data and '</html>' in data) + self.assertTrue('<body' in data and '</body>' in data) + # make sure encoding is specified + self.assertTrue('<meta' in data and 'charset=' in data) + + def test_id_contains_no_special_characters(self): + data = htmlhandling.gen_id('\\tau!\'{}][~^') + for character in {'!', "'", '\\', '{', '}'}: + self.assertFalse(character in data) + + def test_formula_can_consist_only_of_numbers_and_id_is_generated(self): + data = htmlhandling.gen_id('9*8*7=504') + self.assertTrue(data.startswith('form')) + self.assertTrue(data.endswith('504')) + + def test_that_empty_ids_raise_exception(self): + self.assertRaises(ValueError, htmlhandling.gen_id, '') + + def test_that_same_characters_are_not_repeated(self): + id = htmlhandling.gen_id("jo{{{{{{{{ha") + self.assertEqual(id, "jo_ha") + + def test_that_ids_are_max_150_characters_wide(self): + id = htmlhandling.gen_id('\\alpha\\cdot\\gamma + ' * 999) + self.assertTrue(len(id) == 150) + + def test_that_ids_start_with_letter(self): + id = htmlhandling.gen_id('{}\\[]ÖÖÖö9343...·tau') + self.assertTrue(id[0].isalpha()) + + def test_that_link_to_external_image_points_to_file_and_formula(self): + with htmlhandling.HtmlImageFormatter() as img: + formatted_img = img.format_excluded(self.pos, '\\tau\\tau', 'foo.png') + expected_id = htmlhandling.gen_id('\\tau\\tau') + external_file = read(excl_filename, 'r', encoding='utf-8') + # find linked formula path + href = re.search('href="(.*?)"', formatted_img) + self.assertTrue(href != None) + # extract path and id from it + self.assertTrue('#' in href.groups()[0]) + path, id = href.groups()[0].split('#') + self.assertEqual(path, excl_filename) + self.assertEqual(id, expected_id) + + # check external file + self.assertTrue('<p id' in external_file) + self.assertTrue('="'+expected_id in external_file) + + + def test_that_link_to_external_image_points_to_file_basepath_and_formula(self): + os.mkdir('basepath') + with htmlhandling.HtmlImageFormatter('basepath') as img: + formatted_img = img.format_excluded(self.pos, '\\tau\\tau', 'foo.png') + expected_id = htmlhandling.gen_id('\\tau\\tau') + # find linked formula path + href = re.search('href="(.*?)"', formatted_img) + self.assertTrue(href != None) + # extract path and id from it + self.assertTrue('#' in href.groups()[0]) + path, id = href.groups()[0].split('#') + self.assertEqual(path, 'basepath/' + excl_filename) + self.assertEqual(id, expected_id) + + def test_height_and_width_is_in_formatted_html_img_tag(self): + data = None + with htmlhandling.HtmlImageFormatter('foo.html') as img: + data = img.get_html_img(self.pos, '\\tau\\tau', 'foo.png') + self.assertTrue('height=' in data and str(self.pos['height']) in data) + self.assertTrue('width=' in data and str(self.pos['width']) in data) + + def test_no_formula_gets_lost_when_reparsing_external_formula_file(self): + with htmlhandling.HtmlImageFormatter() as img: + img.format_excluded(self.pos, '\\tau' * 999, 'foo.png') + with htmlhandling.HtmlImageFormatter() as img: + img.format_excluded(self.pos, '\\pi' * 666, 'foo_2.png') + data = read(excl_filename) + self.assertTrue('\\tau' in data) + self.assertTrue('\\pi' in data) + + def test_too_long_formulas_are_not_outsourced_if_not_configured(self): + with htmlhandling.HtmlImageFormatter('foo.html') as img: + img.format(self.pos, '\\tau' * 999, 'foo.png') + self.assertFalse(os.path.exists('foo.html')) + + def test_that_too_long_formulas_get_outsourced_if_configured(self): + with htmlhandling.HtmlImageFormatter() as img: + img.set_max_formula_length(90) + img.set_exclude_long_formulas(True) + img.format(self.pos, '\\tau' * 999, 'foo.png') + self.assertTrue(os.path.exists(excl_filename)) + data = read(htmlhandling.HtmlImageFormatter.EXCLUSION_FILE_NAME) + self.assertTrue('\\tau\\tau' in data) + + def test_url_is_included(self): + prefix = "http://crustulus.de/blog" + with htmlhandling.HtmlImageFormatter('foo.html') as img: + img.set_url(prefix) + data = img.format(self.pos, '\epsilon<0', 'foo.png') + self.assertTrue( prefix in data) + + def test_url_doesnt_contain_double_slashes(self): + prefix = "http://crustulus.de/blog/" + with htmlhandling.HtmlImageFormatter('foo.html') as img: + img.set_url(prefix) + data = img.format(self.pos, r'\gamma\text{strahlung}', 'foo.png') + self.assertFalse('//' in data.replace('http://','ignore')) + + # depth is used as negative offset, so negative depth should result in + # positive offset + def test_that_negative_depth_results_in_positive_offset(self): + self.pos['depth'] = '-999' + with htmlhandling.HtmlImageFormatter('foo.html') as img: + data = img.format(self.pos, r'\gamma\text{strahlung}', 'foo.png') + self.assertTrue('align: ' + str(self.pos['depth'])[1:] in data) + + def test_that_displaymath_is_set_or_unset(self): + with htmlhandling.HtmlImageFormatter('foo.html') as img: + data = img.format(self.pos, r'\gamma\text{strahlung}', 'foo.png', + True) + self.assertTrue('="displaymath' in data) + data = img.format(self.pos, r'\gamma\text{strahlung}', 'foo.png', + False) + self.assertTrue('="inlinemath' in data) + + def test_that_alternative_css_class_is_set_correctly(self): + with htmlhandling.HtmlImageFormatter('foo.html') as img: + img.set_display_math_css_class('no1') + img.set_inline_math_css_class('no2') + data = img.format(self.pos, r'\gamma\text{strahlung}', 'foo.png', + True) + self.assertTrue('="no1"' in data) + data = img.format(self.pos, r'\gamma\text{strahlung}', 'foo.png', + False) + self.assertTrue('="no2' in data) + + def test_that_unicode_is_replaced_if_requested(self): + with htmlhandling.HtmlImageFormatter('foo.html') as img: + img.set_replace_nonascii(True) + data = img.format(self.pos, '←', 'foo.png') + self.assertTrue('\\leftarrow' in data, + 'expected: "\\leftarrow" to be in "%s"' % data) + + def test_that_unicode_is_kept_if_not_requested_to_replace(self): + with htmlhandling.HtmlImageFormatter('foo.html') as img: + img.set_replace_nonascii(False) + data = img.format(self.pos, '←', 'foo.png') + self.assertTrue('←' in data) + + def test_formatting_commands_are_stripped(self): + with htmlhandling.HtmlImageFormatter('foo.html') as img: + data = img.format(self.pos, 'a\,b\,c\,d', 'foo.png') + self.assertTrue('a b c d' in data) + data = img.format(self.pos, 'a\,b\;c\ d', 'foo.png') + self.assertTrue('a b c d' in data) + + data = img.format(self.pos, '\Big\{foo\Big\}', 'foo.png') + self.assertTrue('\{foo' in data and '\}' in data) + data = img.format(self.pos, r'\left\{foo\right\}', 'foo.png') + self.assertTrue('\{' in data and 'foo' in data and '\}' in data) + + +def htmleqn(formula, hr=True): + """Format a formula to appear as if it would have been outsourced into an + external file.""" + return '%s\n<p id="%s"><pre>%s</pre></span></p>\n' % (\ + ('<hr/>' if hr else ''), htmlhandling.gen_id(formula), formula) + + +class OutsourcingParserTest(unittest.TestCase): + def setUp(self): + self.html = ('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"' + + '\n "http://www.w3.org/TR/html4/strict.dtd">\n<html>\n<head>\n' + + '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>' + + '<title>Outsourced Formulas</title></head>\n<body>\n<h1>heading</h1>') + + def get_html(self, string): + """Create html string with head / tail und put the specified string into + it.""" + return self.html + string + '\n</body>\n</html>' + + + def test_formulas_are_recognized(self): + data = self.get_html(htmleqn('\\tau')) + parser = htmlhandling.OutsourcedFormulaParser() + parser.feed(data) + self.assertEqual(len(parser.get_formulas()), 1) + + def test_formula_doesnt_contain_surrounding_rubbish(self): + data = self.get_html(htmleqn('\\gamma')) + parser = htmlhandling.OutsourcedFormulaParser() + parser.feed(data) + self.assertEqual(len(parser.get_formulas()), 1) + key = next(iter(parser.get_formulas())) + par = parser.get_formulas()[key] + self.assertFalse('<h1' in par) + self.assertFalse('body>' in par) + self.assertFalse('hr' in par) + + def test_that_header_is_parsed_correctly(self): + p = htmlhandling.OutsourcedFormulaParser() + p.feed(self.get_html(htmleqn('test123', False))) + head = p.get_head() + self.assertTrue('DOCTYPE' in head) + self.assertTrue('<html' in head) + self.assertTrue('<title' in head) + self.assertTrue('</title' in head) + self.assertTrue('</head' in head) + self.assertTrue('<meta' in head) + self.assertTrue('charset=' in head) + + def test_multiple_formulas_are_recognized_correctly(self): + p = htmlhandling.OutsourcedFormulaParser() + p.feed(self.get_html(htmleqn('\\tau', False) + '\n' + + htmleqn('\\gamma') + '\n' + htmleqn('\\epsilon<0'))) + forms = p.get_formulas() + self.assertEqual(len(forms), 3) + self.assertTrue('\\gamma' in forms.values()) + self.assertTrue('\\gamma' in forms.values()) + self.assertTrue('\\epsilon<0' in forms.values()) + diff --git a/support/gladtex/tests/test_imagecreation.py b/support/gladtex/tests/test_imagecreation.py new file mode 100644 index 0000000000..83f87b22d2 --- /dev/null +++ b/support/gladtex/tests/test_imagecreation.py @@ -0,0 +1,165 @@ +#pylint: disable=too-many-public-methods,import-error,too-few-public-methods,missing-docstring,unused-variable +import os +import pprint +import shutil +import tempfile +import unittest +from unittest.mock import patch +from subprocess import SubprocessError + +import gleetex.image as image +from gleetex.image import Format +from gleetex.typesetting import LaTeXDocument as doc + +LATEX_ERROR_OUTPUT = r""" +This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2016/Debian) (preloaded format=latex) + restricted \write18 enabled. +entering extended mode +(./bla.tex +LaTeX2e <2016/03/31> patch level 3 +Babel <3.9r> and hyphenation patterns for 10 language(s) loaded. +(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls +Document Class: article 2014/09/29 v1.4h Standard LaTeX document class +(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo)) (./bla.aux) +! Undefined control sequence. +<recently read> \foo + +l.3 $\foo + $ +No pages of output. +Transcript written on bla.log. +""" + + +def call_dummy(_lklklklklk, **blah): + """Dummy to prohibit subprocess execution.""" + return str(blah) + + + + + +#pylint: disable=unused-argument +def latex_error_mock(_cmd, **quark): + """Mock an error case.""" + raise SubprocessError(LATEX_ERROR_OUTPUT) + +#pylint: disable=unused-argument +def dvipng_mock(cmd, **kwargs): + """Mock an error case.""" + fn = None + try: + fn = next(e for e in cmd if e.endswith('.png')) + except StopIteration: + try: + fn = next(e for e in cmd if e.endswith('.dvi')) + except StopIteration: + pass + if fn: + with open(fn, 'w') as f: + f.write("test case") + return 'This is dvipng 1.14 Copyright 2002-2010 Jan-Ake Larsson\n ' + \ + 'depth=3 height=9 width=22' + +def touch(files): + for file in files: + dirname = os.path.dirname(file) + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + with open(file, 'w') as f: + f.write('\n') + + +class test_imagecreation(unittest.TestCase): + def setUp(self): + self.original_directory = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + os.chdir(self.tmpdir) + image.Tex2img.call = call_dummy + + def tearDown(self): + os.chdir(self.original_directory) + shutil.rmtree(self.tmpdir, ignore_errors=True) + + @patch('gleetex.image.proc_call', latex_error_mock) + def test_that_error_of_incorrect_formula_is_parsed_correctly(self): + i = image.Tex2img(Format.Png) + try: + i.create_dvi(doc("\\foo"), 'foo.png') + except SubprocessError as e: + # expect undefined control sequence in error output + self.assertTrue('Undefined' in e.args[0]) + + @patch('gleetex.image.proc_call', call_dummy) + def test_that_intermediate_files_are_removed_after_successful_run(self): + files = ['foo.log', 'foo.aux', 'foo.tex'] + touch(files) + i = image.Tex2img(Format.Png) + i.create_dvi(doc("\\frac\\pi\\tau"), 'foo.png') + for intermediate_file in files: + self.assertFalse(os.path.exists(intermediate_file), "File " + + intermediate_file + " should not exist.") + + @patch('gleetex.image.proc_call', latex_error_mock) + def test_that_intermediate_files_are_removed_when_exception_is_raised(self): + files = ['foo.log', 'foo.aux', 'foo.tex'] + touch(files) + # error case + i = image.Tex2img(Format.Png) + try: + i.convert(doc("\\foo"), 'foo') + except SubprocessError as e: + for intermediate_file in files: + self.assertFalse(os.path.exists(intermediate_file), "File " + + intermediate_file + " should not exist.") + + @patch('gleetex.image.proc_call', dvipng_mock) + def test_intermediate_files_are_removed(self): + files = ['foo.tex', 'foo.log', 'foo.aux', 'foo.dvi'] + touch(files) + i = image.Tex2img(Format.Png) + i.convert(doc('\\hat{x}'), 'foo') + for intermediate_file in files: + self.assertFalse(os.path.exists(intermediate_file)) + + @patch('gleetex.image.proc_call', latex_error_mock) + def test_intermediate_files_are_removed_when_exception_raised(self): + files = ['foo.tex', 'foo.log', 'foo.aux', 'foo.dvi'] + touch(files) + i = image.Tex2img(Format.Png) + try: + i.convert(doc('\\hat{x}'), 'foo') + except SubprocessError: + self.assertFalse(os.path.exists('foo.tex')) + self.assertFalse(os.path.exists('foo.dvi')) + self.assertFalse(os.path.exists('foo.log')) + self.assertFalse(os.path.exists('foo.aux')) + + + @patch('gleetex.image.proc_call', lambda *x, **y: 'This is dvipng 1.14 ' + \ + 'Copyright 2002-2010 Jan-Ake Larsson\n depth=3 height=9 width=22') + def test_that_values_for_positioning_png_are_returned(self): + i = image.Tex2img(Format.Png) + posdata = i.create_image('foo.dvi') + self.assertTrue('height' in posdata) + self.assertTrue('width' in posdata) + + + @patch('gleetex.image.proc_call', dvipng_mock) + def test_that_output_file_names_with_paths_are_ok_and_log_is_removed(self): + fname = lambda f: os.path.join('bilder', 'farce.' + f) + touch([fname('log'), fname('png')]) + t = image.Tex2img(Format.Png) + t.convert(doc(r"\hat{es}\pi\pi\ldots"), fname('')[:-1]) + self.assertFalse(os.path.exists("farce.log")) + self.assertTrue(os.path.exists(fname('png')), + "couldn't find file {}, directory structure:\n{}".format( + fname('png'), ''.join(pprint.pformat(list(os.walk('.')))))) + self.assertFalse(os.path.exists(fname('log'))) + + +class TestImageResolutionCorrectlyCalculated(unittest.TestCase): + def test_sizes_are_correctly_calculated(self): + self.assertEqual(int(image.fontsize2dpi(12)), 115) + self.assertEqual(int(image.fontsize2dpi(10)), 96) + diff --git a/support/gladtex/tests/test_typesetting.py b/support/gladtex/tests/test_typesetting.py new file mode 100644 index 0000000000..374fe38511 --- /dev/null +++ b/support/gladtex/tests/test_typesetting.py @@ -0,0 +1,218 @@ +#pylint: disable=too-many-public-methods,import-error,too-few-public-methods,missing-docstring,unused-variable +import unittest +from gleetex.typesetting import LaTeXDocument +import gleetex.typesetting as typesetting + +class test_typesetting(unittest.TestCase): + def test_formula_is_embedded(self): + formula = 'E = m \\cdot c^2' + doc = LaTeXDocument(formula) + self.assertTrue(formula in str(doc), + "formula must be contained in LaTeX typesetting as it was inserted.") + + def test_if_displaymath_unset_correct_env_used(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_displaymath(False) + self.assertTrue('\\(' in str(doc)) + self.assertTrue('\\)' in str(doc)) + + def test_if_displaymath_is_set_correct_env_used(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_displaymath(True) + self.assertTrue('\\[' in str(doc)) + self.assertTrue('\\]' in str(doc)) + + def test_preamble_is_included(self): + preamble = '\\usepackage{eurosym}' + doc = LaTeXDocument('moooo') + doc.set_preamble_string(preamble) + self.assertTrue(preamble in str(doc)) + + def test_obviously_wrong_encoding_trigger_exception(self): + doc = LaTeXDocument('f00') + self.assertRaises(ValueError, doc.set_encoding, 'latin1:') + self.assertRaises(ValueError, doc.set_encoding, 'utf66') + # the following passes (assertRaisesNot) + doc.set_encoding('utf-8') + + def test_that_latex_maths_env_is_used(self): + doc = LaTeXDocument('f00') + doc.set_latex_environment('flalign*') + self.assertTrue(r'\begin{flalign*}' in str(doc)) + self.assertTrue(r'\end{flalign*}' in str(doc)) + +################################################################################ + + +class test_replace_unicode_characters(unittest.TestCase): + def test_that_ascii_strings_are_returned_verbatim(self): + for string in ['abc.\\', '`~[]}{:<>']: + textmode = typesetting.replace_unicode_characters(string, False) + self.assertEqual(textmode, string, 'expected %s, got %s' % (string, textmode)) + mathmode = typesetting.replace_unicode_characters(string, True) + self.assertEqual(textmode, string, 'expected %s, got %s' % (string, mathmode)) + + def test_that_alphabetical_characters_are_replaced_by_default(self): + textmode = typesetting.replace_unicode_characters('ö', False) + self.assertTrue('\\"' in textmode) + mathmode = typesetting.replace_unicode_characters('ö', True) + self.assertTrue('\\ddot' in mathmode) + + def test_that_alphabetical_characters_are_kept_in_text_mode_if_specified(self): + self.assertEqual(typesetting.replace_unicode_characters('ö', False, # text mode + replace_alphabeticals=False), 'ö') + self.assertEqual(typesetting.replace_unicode_characters('æ', False, + replace_alphabeticals=False), 'æ') + + def test_that_alphanumericals_are_replaced_in_mathmode_even_if_replace_alphabeticals_set(self): + self.assertNotEqual(typesetting.replace_unicode_characters('öäü', True, + replace_alphabeticals=True), 'öäü') + self.assertNotEqual(typesetting.replace_unicode_characters('æø', True, + replace_alphabeticals=True), 'æø') + + + def test_that_charachters_not_present_in_file_raise_exception(self): + with self.assertRaises(ValueError): + typesetting.replace_unicode_characters('€', True) + + def test_that_formulas_are_replaced(self): + self.assertNotEqual(typesetting.replace_unicode_characters('π', True), + 'π') + self.assertNotEqual(typesetting.replace_unicode_characters('π', False), + 'π') + +class test_get_matching_brace(unittest.TestCase): + def test_closing_brace_found_when_only_one_brace_present(self): + text = 'text{ok}' + self.assertEqual(typesetting.get_matching_brace(text, 4), len(text) - 1) + self.assertEqual(typesetting.get_matching_brace(text + 'foo', 4), len(text) - 1) + + def test_outer_brace_found(self): + text = 'text{o, bla\\"{o}dfdx.}ds' + self.assertEqual(typesetting.get_matching_brace(text, 4), len(text)-3) + + def test_inner_brace_is_matched(self): + text = 'text{o, bla\\"{o}dfdx.}ds' + self.assertEqual(typesetting.get_matching_brace(text, 13), 15) + + def test_that_unmatched_braces_raise_exception(self): + with self.assertRaises(ValueError): + typesetting.get_matching_brace('text{foooooooo', 4) + with self.assertRaises(ValueError): + typesetting.get_matching_brace('text{jo\"{o....}', 4) + + def test_wrong_position_for_opening_brace_raises(self): + with self.assertRaises(ValueError): + typesetting.get_matching_brace('moo', 1) + + +class test_escape_unicode_maths(unittest.TestCase): + """These tests assume that the tests written above work!""" + def test_that_mathmode_and_textmode_are_treated_differently(self): + math = typesetting.escape_unicode_maths('ö') + self.assertNotEqual(math, 'ö') + text = typesetting.escape_unicode_maths('\\text{ö}') + self.assertFalse('ö' in text) + # check whether characters got transcribed differently; it's enough to + # check one character of the generated sequence, they should differ + self.assertNotEqual(math[:2], text[6:8]) + + def test_that_flag_to_preserve_alphas_is_passed_through(self): + res = typesetting.escape_unicode_maths('\\text{ö}', + replace_alphabeticals=False) + self.assertEqual(res, '\\text{ö}') + + def test_that_all_characters_are_preserved_when_no_replacements_happen(self): + text = 'This is a \\text{test} mate.' + self.assertEqual(typesetting.escape_unicode_maths(text), text) + self.assertEqual(typesetting.escape_unicode_maths(text, + replace_alphabeticals=False), text) + text = 'But yeah but no' * 20 + ', oh my god!' + self.assertEqual(typesetting.escape_unicode_maths(text), text) + self.assertEqual(typesetting.escape_unicode_maths(text, + replace_alphabeticals=False), text) + + def test_that_everything_around_surrounded_character_is_preserved(self): + text = 'This is a \\text{über} test. ;)' + result = typesetting.escape_unicode_maths(text, + replace_alphabeticals=True) + ue_pos = text.index('ü') + # text in front is unchanged + self.assertEqual(result[:ue_pos], text[:ue_pos]) + # find b character, which is the start of the remaining string + b_pos = result[ue_pos:].find('b') + ue_pos + # check that text after umlaut matches + self.assertEqual(result[b_pos:], text[ue_pos+1:]) + + text = 'But yeah but no' * 20 + ', oh my god!ø' + o_strok_pos = text.index('ø') + res = typesetting.escape_unicode_maths(text) + self.assertEqual(res[:o_strok_pos], text[:o_strok_pos]) + + def test_that_unknown_unicode_characters_raise_exception(self): + # you know that Santa Clause character? Seriously, if you don't know it, + # you should have a look. LaTeX does indeed not have command for this + # (2016, one never knows) + santa = chr(127877) + with self.assertRaises(typesetting.DocumentSerializationException): + typesetting.escape_unicode_maths(santa) + + def test_that_two_text_environments_preserve_all_characters(self): + text = r'a\cdot b \text{equals} b\cdot c} \mbox{ is not equal } u^{v\cdot k}' + self.assertEqual(typesetting.escape_unicode_maths(text), text) + + def test_color_names_in_backgroundare_accepted(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_background_color('cyan') + doc = str(doc) + self.assertTrue('pagecolor{cyan}' in doc, + "Expected \\pagecolor in document, got: %s" % doc) + self.assertTrue('\\color' not in doc) + self.assertFalse('definecolor' in doc) + + def test_color_names_in_foregroundare_accepted(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_foreground_color('cyan') + doc = str(doc) + self.assertTrue('pagecolor' not in doc, + "Expected \\pagecolor in document, got: %s" % doc) + self.assertTrue('\\color{cyan' in doc, + "expeccted \\color{cyan, got:\n" + doc) + self.assertFalse('definecolor' in doc) + + def test_hex_colours_with_leading_0s_work(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_background_color('00FFCC') + doc = str(doc) + self.assertTrue('pagecolor{background}' in doc, + "Expected \\pagecolor in document, got: %s" % doc) + self.assertTrue('definecolor' in doc) + self.assertTrue('00FFCC' in doc) + + def test_color_rgb_in_foregroundare_accepted(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_foreground_color('FFAACC') + doc = str(doc) + self.assertTrue('pagecolor{}' not in doc, + "Expected \\pagecolor in document, got: %s" % doc) + self.assertTrue('\\color{foreground' in doc, + "document misses \\color command: %s" % doc) + self.assertTrue('definecolor' in doc) + self.assertTrue('FFAACC' in doc) + + def test_color_rgb_in_backgroundare_accepted(self): + doc = LaTeXDocument(r'A = \pi r^2') + doc.set_background_color('FFAACC') + doc = str(doc) + self.assertTrue('pagecolor{background}' in doc, + "Expected \\pagecolor in document, got: %s" % doc) + self.assertTrue('\\color' not in doc) + self.assertTrue('definecolor' in doc) + self.assertTrue('FFAACC' in doc) + + def test_no_colors_no_color_definitions(self): + doc = str(LaTeXDocument(r'A = \pi r^2')) + self.assertFalse('pagecolor' in doc) + self.assertFalse('\\color' in doc) + self.assertFalse('definecolor' in doc) + diff --git a/support/gladtex/update_unicode_table.py b/support/gladtex/update_unicode_table.py new file mode 100644 index 0000000000..f32bc19fcd --- /dev/null +++ b/support/gladtex/update_unicode_table.py @@ -0,0 +1,142 @@ +"""This script auto-generates gleetex/unicode_data.py. The purpose is to provide a +table with mappings from unicode points to their LaTeX equivalent. This way, +formulas can be converted using LaTeX2e, but the end-user can still use unicode +in it's formulas. The unicode version can also be used to make the alternative +text more readable.""" + +import collections +import enum +import os +import urllib.request +import xml.etree.ElementTree as ET + +################################################################################ +# Constants + +class LaTeXMode(enum.Enum): + # exception: not a constant, but required for one of the constants + """Represent either math or text mode. Math mode in LaTeX is e.g. + everything between $ and $.""" + textmode = 0 + mathmode = 1 + +# URL to XML file, which is used to generate the python source file +UNICODE_TABLE_URL = "https://raw.githubusercontent.com/w3c/xml-entities/gh-pages/unicode.xml" +# a list of commands to replace, if found +BAD_COMMANDS = { + # decimal_codepoint: {LaTeXMode: new version} + 178: {LaTeXMode.mathmode: '^2'}, + 179: {LaTeXMode.mathmode: '^3'}, + 181: {LaTeXMode.mathmode: '\\mu'}, + 185: {LaTeXMode.mathmode: '^1'}, + 8211: {LaTeXMode.mathmode: '\\mathrm{\\textendash}'}, + 8722: {LaTeXMode.mathmode: '-'} +} + +################################################################################ + +def get_unicode_table_xml(): + with urllib.request.urlopen(UNICODE_TABLE_URL) as u: + return ET.fromstring(u.read()) + +def create_unicode_latex_table(root): + """This function iterates over the XML tree and extracts all characters for + the unicode table. The resulting table will have the decimal unicode point + as key. The value is again a dict with the possible keys from LaTeX and the + LaTeX commands as string. + Certain unicode points are ignored, to prevent replacing normal or control + characters.""" + unicode_table = {} + for character in root.find('charlist').iterfind('character'): + childtags = set(node.tag for node in character.getchildren()) + # skip characters without LaTeX alternative + if 'latex' not in childtags and 'AMS' not in childtags and \ + 'mathlatex' not in childtags: + continue # skip this character + attr = character.attrib.get + # if no mode (text or math) was specified, ignore character + if attr('mode') not in ('text', 'math', 'mixed', 'other'): + continue + + # a defined character may have multiple codepoints (called ids); add + # each of the ids as a separate entry to the table + ids = tuple(map(int, attr('dec').split('-'))) + if any(elem for elem in ids if elem < 161): + continue # ignore ASCII and a few control unicode characters + + # extract textmode, mathmode and AMS commands: + commands = {} + if 'latex' in childtags: + commands[LaTeXMode.textmode] =\ + next(character.iterfind('latex')).text + if 'AMS' in childtags: + commands[LaTeXMode.mathmode] = next(character.iterfind('AMS')).text + # only take LaTeX command from <mathlatex/>, if no AMS tag present and + # no set was specified. A `set` is a attempt to specify the LaTeX + # package which needs to be loaded. + if 'mathlatex' in childtags and LaTeXMode.mathmode not in commands: + mathnode = next(character.iterfind('mathlatex')) + if 'set' not in mathnode.attrib: + commands[LaTeXMode.mathmode] = mathnode.text + + if commands: # if a usable textmode and a mathmode without unicode-math found: + for identification in ids: + # some code points are not usable for our purposes, so update + # the control sequences, if appropriate + if identification in BAD_COMMANDS: + commands.update(BAD_COMMANDS[identification]) + unicode_table[identification] = commands + return unicode_table + +def serialize_table(table): + """Serialize the given unicode table to a python table, which could be + directly executed by eval. The decimal code points, serving as a key in the + dictionary, are sorted for the output.""" + ordered_table = collections.OrderedDict() + for key in sorted(table.keys()): + ordered_table[key] = table[key] + python_string = ['unicode_table = {'] + reprmode = lambda m, v: 'LaTeXMode.%s: %s' % (m.name, repr(v[m])) + for code_point, replacements in ordered_table.items(): + # serialize by hand to have a fixed order of items; helpful for a + # minimal git diff + commands = '' + if LaTeXMode.textmode in replacements: + commands = reprmode(LaTeXMode.textmode, replacements) + if LaTeXMode.mathmode in replacements: + if commands: + commands += ', ' + commands += reprmode(LaTeXMode.mathmode, replacements) + python_string.append('%s: {%s},' % (code_point, commands)) + return '\n '.join(python_string) + '\n }\n' + +def generate_python_src_file(table, python_table): + """Generate a fully importable python source file, by dumping the enum + declarations, python imports, doc strings and the given python string with + the unicode table into the source and returning it as a whole string.""" + enum_def = 'class LaTeXMode(enum.Enum):\n """%s"""\n ' % LaTeXMode.__doc__ + enum_values = tuple(e for e in dir(LaTeXMode) if not e.startswith('_')) + enum_def += '\n '.join('%s = %s' % (name, getattr(LaTeXMode, name).value) + for name in enum_values) + return """\"\"\" +DO NOT ALTER THIS FILE IN ANY WAY, IT IS GENERATED AUTOMATICALLY. SEE THE SCRIPT +`update_unicode_table.py` FOR MORE INFORMATION. + +This file contains a table of unicode code point to LaTeX command mapping. It +has %s entries and was derived from +<%s>.\"\"\" +#pylint: disable=too-many-lines,missing-docstring\n\n +import enum\n +%s\n\n%s\n""" % (len(table), UNICODE_TABLE_URL, enum_def, python_table) + + +def main(): + if not os.path.exists('gleetex'): + print("Error: Generator script must be run from GladTeX source root.") + table = create_unicode_latex_table(get_unicode_table_xml()) + python_table = serialize_table(table) + with open('gleetex/unicode.py', 'w', encoding='utf-8') as f: + f.write(generate_python_src_file(table, python_table)) + +if __name__ == '__main__': + main() |