#! /usr/bin/env python # -*- coding: utf-8 -*- # file generate_symbols_images.py # This file is part of LyX, the document processor. # Licence details can be found in the file COPYING. # author Georg Baum # Full author contact details are available in file CREDITS # This script generates a toolbar image for each missing math symbol # It needs the template document generate_symbols_images.lyx, which must # contain the placeholder formula '$a$' for generating the png image via # preview.sty and dvipng. # The created images are not always optimal, therefore the existing manually # created images should never be replaced by automatically created ones. from __future__ import print_function import os, re, string, sys, subprocess, tempfile, shutil import Image import io def usage(prog_name): return ("Usage: %s lyxexe outputpath\n" % prog_name) def error(message): sys.stderr.write(message + '\n') sys.exit(1) def getlist(lyxexe, lyxfile): """ Call LyX and get a list of symbols from mathed debug output. This way, we can re-use the symbols file parser of LyX, and do not need to reimplement it in python. """ # The debug is only generated if lyxfile contains a formula cmd = "%s %s -dbg mathed -x lyx-quit" % (lyxexe, lyxfile) proc = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE) (stdout, stderr) = proc.communicate() regexp = re.compile(r'.*: read symbol \'(\S+)\s+inset:\s+(\S+)\s+draw:\s+(\S*)\s+extra:\s+(\S+)') # These insets are more complex than simply symbols, so the images need to # be created manually skipinsets = ['big', 'font', 'lyxblacktext', 'matrix', 'mbox', 'oldfont', \ 'ref', 'split', 'space', 'style'] mathsymbols = [] textsymbols = [] for line in stderr.split('\n'): m = regexp.match(line) if m: inset = m.group(2) if not inset in skipinsets: if m.group(4) == 'textmode': textsymbols.append(m.group(1)) else: mathsymbols.append(m.group(1)) return (mathsymbols, textsymbols) def getreplacements(filename): replacements = {} replacements['|'] = 'vert' replacements['/'] = 'slash' replacements['\\'] = 'backslash' replacements['*'] = 'ast' replacements['AA'] = 'textrm_AA' replacements['O'] = 'textrm_O' cppfile = io.open(filename, 'r', encoding='utf_8') regexp = re.compile(r'.*"([^"]+)",\s*"([^"]+)"') found = False for line in cppfile.readlines(): if found: m = regexp.match(line) if m: replacements[m.group(1)] = m.group(2) else: return replacements elif line.find('ImgMap sorted_img_map') == 0: found = True def gettoolbaritems(filename): items = [] uifile = io.open(filename, 'r', encoding='utf_8') regexp = re.compile(r'.*Item "([^"\[]+)(\[\[[^\]]+\]\])?"\s*"math-insert\s+([^"]+)"') for line in uifile.readlines(): m = regexp.match(line) if m: if '\\' + m.group(1) == m.group(3): items.append(m.group(1)) return items def getmakefileentries(filename): items = [] makefile = io.open(filename, 'r', encoding='utf_8') regexp = re.compile(r'.*images/math/(.+)\.(png|svgz)') for line in makefile.readlines(): m = regexp.match(line) if m: items.append(m.group(1)) return items def createimage(name, path, template, lyxexe, tempdir, math, replacements, toolbaritems, makefileentries): """ Create the image file for symbol name in path. """ if name in replacements.keys(): filename = replacements[name] elif name.startswith('lyx'): print('Skipping ' + name) return else: skipchars = ['|', '/', '\\', '*', '!', '?', ':', ';', '^', '<', '>'] for i in skipchars: if name.find(i) >= 0: print( 'Skipping ' + name) return filename = name pngname = os.path.join(path, filename + '.png') if name in toolbaritems: if filename in makefileentries: suffix = ' (found in toolbar and makefile)' else: suffix = ' (found in only in toolbar)' else: if filename in makefileentries: suffix = ' (found only in makefile)' else: suffix = ' (not found)' if os.path.exists(pngname): print('Skipping ' + name + suffix) return print('Generating ' + name + suffix) lyxname = os.path.join(tempdir, filename) lyxfile = io.open(lyxname + '.lyx', 'w', encoding='utf_8') if math: lyxfile.write(template.replace('$a$', '$\\' + name + '$')) else: lyxfile.write(template.replace('$a$', '$\\text{\\' + name + '}$')) lyxfile.close() cmd = "%s %s.lyx -e dvi" % (lyxexe, lyxname) proc = subprocess.Popen(cmd, shell=True) proc.wait() if proc.returncode != 0: print('Error in DVI creation for ' + name) return # The magnifaction factor is calculated such that we get an image of # height 18 px for most symbols and document font size 11. Then we can # add a small border to get the standard math image height of 20 px. cmd = "dvipng %s.dvi -bg Transparent -D 115 -o %s" % (lyxname, pngname) proc = subprocess.Popen(cmd, shell=True) proc.wait() if proc.returncode != 0: print('Error in PNG creation for ' + name) return image = Image.open(pngname) (width, height) = image.size if width < 20 and height < 20: if width == 19 and height == 19: padded = Image.new('RGBA', (width+1, height+1), (0, 0, 0, 0)) padded.paste(image, (0, 0)) elif width == 19: padded = Image.new('RGBA', (width+1, height+2), (0, 0, 0, 0)) padded.paste(image, (0, 1)) elif height == 19: padded = Image.new('RGBA', (width+2, height+1), (0, 0, 0, 0)) padded.paste(image, (1, 0)) else: padded = Image.new('RGBA', (width+2, height+2), (0, 0, 0, 0)) padded.paste(image, (1, 1)) padded.convert(image.mode) padded.save(pngname, "PNG") def main(argv): if len(argv) == 3: (base, ext) = os.path.splitext(argv[0]) (mathsymbols, textsymbols) = getlist(argv[1], base) cppfile = os.path.join(os.path.dirname(base), '../../src/frontends/qt4/GuiApplication.cpp') replacements = getreplacements(cppfile) uifile = os.path.join(os.path.dirname(base), '../../lib/ui/stdtoolbars.inc') toolbaritems = gettoolbaritems(uifile) makefile = os.path.join(os.path.dirname(base), '../../lib/Makefile.am') makefileentries = getmakefileentries(makefile) lyxtemplate = base + '.lyx' templatefile = io.open(base + '.lyx', 'r', encoding='utf_8') template = templatefile.read() templatefile.close() tempdir = tempfile.mkdtemp() for i in mathsymbols: createimage(i, argv[2], template, argv[1], tempdir, True, replacements, toolbaritems, makefileentries) for i in textsymbols: createimage(i, argv[2], template, argv[1], tempdir, False, replacements, toolbaritems, makefileentries) shutil.rmtree(tempdir) else: error(usage(argv[0])) return 0 if __name__ == "__main__": main(sys.argv)