Skip to content

Instantly share code, notes, and snippets.

@goldbattle
Created June 11, 2019 13:32
Show Gist options
  • Save goldbattle/c230bfcc56efdbb3442f385f486a5547 to your computer and use it in GitHub Desktop.
Save goldbattle/c230bfcc56efdbb3442f385f486a5547 to your computer and use it in GitHub Desktop.
#
# This file is part of m.css.
#
# Copyright © 2017, 2018, 2019 Vladimír Vondruš <mosra@centrum.cz>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
import html
import pickle
import re
from hashlib import sha1
import latex2svg
# Extracted common code used by both doxygen.py and the m.math plugin to
# avoid dependency of doxygen.py on Pelican
# Modified params to use for math rendering
params = latex2svg.default_params.copy()
params.update({
# Don't use libertine fonts as they mess up things. The latex xcolor
# package version < 2.12 (the one on Ubuntu 16.04) has a bug that prevents
# lowercase `f` being used for color names (uppercase works):
# https://tex.stackexchange.com/a/274531. However, when replacing the
# colors with CSS classes later (_class_mapping below), the hexadecimal
# number needs to be lowercase again.
'preamble': r"""
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}
\usepackage{gensymb}
\usepackage{newtxtext}
\usepackage{xcolor}
\definecolor{m-default}{HTML}{caFe03}
\definecolor{m-primary}{HTML}{caFe04}
\definecolor{m-success}{HTML}{caFe05}
\definecolor{m-warning}{HTML}{caFe06}
\definecolor{m-danger}{HTML}{caFe07}
\definecolor{m-info}{HTML}{caFe08}
\definecolor{m-dim}{HTML}{caFe09}
""",
# Zoom the letters a bit to match page font size.
# TODO dvisvgm 2.2.2 was changed to "avoid scientific notation of floating
# point numbers" (https://github.com/mgieseki/dvisvgm/blob/3facb925bfe3ab47bf40d376d567a114b2bee3a5/NEWS#L90),
# meaning the default precision (6) is now used for the decimal points, so
# you'll often get numbers like 194.283203, which is a bit too much
# precision. Could be enough to have 4 decimal points max, but that would
# be too little for <2.2.2, where it would mean you get just 194.28. We
# need to detect the version somehow and then apply reasonable precision
# based on that.
'dvisvgm_cmd': 'dvisvgm --no-fonts -Z 1.25',
})
# Mapping from color codes to CSS classes. Keep in sync with m.plots.
_class_mapping = {
('fill=\'#cafe03\'', 'class=\'m-default\''),
('fill=\'#cafe04\'', 'class=\'m-primary\''),
('fill=\'#cafe05\'', 'class=\'m-success\''),
('fill=\'#cafe06\'', 'class=\'m-warning\''),
('fill=\'#cafe07\'', 'class=\'m-danger\''),
('fill=\'#cafe08\'', 'class=\'m-info\''),
('fill=\'#cafe09\'', 'class=\'m-dim\'')
}
_patch_src = re.compile(r"""<\?xml version='1\.0'\?>
<!-- This file was generated by dvisvgm \d+\.\d+\.\d+ -->
<svg height='(?P<height>[^']+)pt' version='1.1' viewBox='(?P<viewBox>[^']+)' width='(?P<width>[^']+)pt' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
""")
# version ignored by all UAs, safe to drop https://stackoverflow.com/a/18468348
_patch_dst = r"""<svg{attribs} style="width: {width:.3f}em; height: {height:.3f}em;{style}" viewBox="{viewBox}">
<title>
{formula}
</title>
"""
# 1 pt is 1.333 px, base font size is 16px. TODO: make this configurable,
# remove the 1.25 scaling
_pt2em = 1.333333/16.0
_unique_src = re.compile(r"""(?P<name> id|xlink:href)='(?P<ref>#?)(?P<id>g\d+-\d+|page\d+)'""")
_unique_dst = r"""\g<name>='\g<ref>eq{counter}-\g<id>'"""
# Counter to ensure unique IDs for multiple SVG elements on the same page.
# Reset back to zero on start of a new page for reproducible behavior.
counter = 0
# Cache for rendered formulas (source formula sha1 -> (depth, svg data)). The
# counter is not included
_cache_version = 0
_cache = None
# Fetch cached formula or render it and add to the cache. The formula has to
# be already wrapped in $, $$ etc. environment.
def fetch_cached_or_render(formula):
global _cache
# Cache not used, pass through
if not _cache:
out = latex2svg.latex2svg(formula, params=params)
return out['depth'], out['svg']
hash = sha1(formula.encode('utf-8')).digest()
if not hash in _cache[2]:
out = latex2svg.latex2svg(formula, params=params)
_cache[2][hash] = (_cache[1], out['depth'], out['svg'])
else:
_cache[2][hash] = (_cache[1], _cache[2][hash][1], _cache[2][hash][2])
return (_cache[2][hash][1], _cache[2][hash][2])
def unpickle_cache(file):
global _cache
if file:
with open(file, 'rb') as f:
_cache = pickle.load(f)
else:
_cache = None
# Reset the cache if not valid or not expected version
if not _cache or _cache[0] != _cache_version:
_cache = (_cache_version, 0, {})
# Otherwise bump cache age
else: _cache = (_cache[0], _cache[1] + 1, _cache[2])
def pickle_cache(file):
global _cache
# Don't save any file if there is nothing
if not _cache or not _cache[2]: return
# Prune entries that were not used
cache_to_save = (_cache_version, _cache[1], {})
for hash, entry in _cache[2].items():
if entry[0] != _cache[1]: continue
cache_to_save[2][hash] = entry
with open(file, 'wb') as f:
pickle.dump(cache_to_save, f)
# Patches the output from dvisvgm
def patch(formula, svg, depth, attribs):
# patch away XML preamble and needless attributes, convert `pt` to `em`,
# add vertical align for inline formulas and add additional attribs like
# CSS classes to the <svg> element
if depth is None: style = ''
else: style = ' vertical-align: -{:.3f}em;'.format(depth*1.25)
def repl(match):
return _patch_dst.format(
width=_pt2em*float(match.group('width')),
height=_pt2em*float(match.group('height')),
style=style,
viewBox=match.group('viewBox'),
attribs=attribs,
formula=html.escape(formula))
svg = _patch_src.sub(repl, svg)
# Make element IDs unique
global counter
counter += 1
svg = _unique_src.sub(_unique_dst.format(counter=counter), svg)
# Replace color codes with CSS classes
for src, dst in _class_mapping: svg = svg.replace(src, dst)
return svg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment