--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/QScintilla/Exporters/ExporterTEX.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an exporter for TeX. +""" + +# This code is a port of the C++ code found in SciTE 1.74 +# Original code: Copyright 1998-2006 by Neil Hodgson <neilh@scintilla.org> + +import os + +from PyQt5.Qsci import QsciScintilla + +from E5Gui import E5MessageBox +from E5Gui.E5OverrideCursor import E5OverrideCursor + +from .ExporterBase import ExporterBase + +import Preferences + + +class ExporterTEX(ExporterBase): + """ + Class implementing an exporter for TeX. + """ + CHARZ = 26 + + def __init__(self, editor, parent=None): + """ + Constructor + + @param editor reference to the editor object (QScintilla.Editor.Editor) + @param parent parent object of the exporter (QObject) + """ + ExporterBase.__init__(self, editor, parent) + + def __getTexRGB(self, color): + """ + Private method to convert a color object to a TeX color string. + + @param color color object to convert (QColor) + @return TeX color string (string) + """ + # texcolor[rgb]{0,0.5,0}{....} + rf = color.red() / 256.0 + gf = color.green() / 256.0 + bf = color.blue() / 256.0 + + # avoid breakage due to locale setting + r = int(rf * 10 + 0.5) + g = int(gf * 10 + 0.5) + b = int(bf * 10 + 0.5) + + return "{0:d}.{1:d}, {2:d}.{3:d}, {4:d}.{5:d}".format( + r // 10, r % 10, g // 10, g % 10, b // 10, b % 10) + + def __texStyle(self, style): + """ + Private method to calculate a style name string for a given style + number. + + @param style style number (integer) + @return style name string (string) + """ + buf = "" + if style >= 0: + start = ord('a') + else: + start = ord('A') + style = abs(style) + + if style == 0: + buf = "a" + else: + while style > 0: + buf += chr(start + (style % self.CHARZ)) + style //= self.CHARZ + return buf + + def __defineTexStyle(self, font, color, paper, file, istyle): + """ + Private method to define a new TeX style. + + @param font the font to be used (QFont) + @param color the foreground color to be used (QColor) + @param paper the background color to be used (QColor) + @param file reference to the open file to write to (file object) + @param istyle style number (integer) + """ + closing_brackets = 3 + file.write( + "\\newcommand{{\\eric{0}}}[1]{{\\noindent{{\\ttfamily{{".format( + self.__texStyle(istyle))) + if font.italic(): + file.write("\\textit{") + closing_brackets += 1 + if font.bold(): + file.write("\\textbf{") + closing_brackets += 1 + if color != self.defaultColor: + file.write( + "\\textcolor[rgb]{{{0}}}{{".format(self.__getTexRGB(color))) + closing_brackets += 1 + if paper != self.defaultPaper: + file.write( + "\\colorbox[rgb]{{{0}}}{{".format(self.__getTexRGB(paper))) + closing_brackets += 1 + file.write("#1{0}\n".format('}' * closing_brackets)) + + def exportSource(self): + """ + Public method performing the export. + """ + filename = self._getFileName(self.tr("TeX Files (*.tex)")) + if not filename: + return + + self.editor.recolor(0, -1) + + tabSize = self.editor.getEditorConfig("TabWidth") + if tabSize == 0: + tabSize = 4 + + onlyStylesUsed = Preferences.getEditorExporter( + "TeX/OnlyStylesUsed") + titleFullPath = Preferences.getEditorExporter( + "TeX/FullPathAsTitle") + + lex = self.editor.getLexer() + self.defaultPaper = ( + lex and + lex.paper(QsciScintilla.STYLE_DEFAULT) or + self.editor.paper().name() + ) + self.defaultColor = ( + lex and + lex.color(QsciScintilla.STYLE_DEFAULT) or + self.editor.color().name() + ) + self.defaultFont = ( + lex and + lex.color(QsciScintilla.STYLE_DEFAULT) or + Preferences.getEditorOtherFonts("DefaultFont") + ) + + lengthDoc = self.editor.length() + styleIsUsed = {} + if onlyStylesUsed: + for index in range(QsciScintilla.STYLE_MAX + 1): + styleIsUsed[index] = False + # check the used styles + pos = 0 + while pos < lengthDoc: + styleIsUsed[self.editor.styleAt(pos) & 0x7F] = True + pos += 1 + else: + for index in range(QsciScintilla.STYLE_MAX + 1): + styleIsUsed[index] = True + styleIsUsed[QsciScintilla.STYLE_DEFAULT] = True + + with E5OverrideCursor(), open(filename, "w", encoding="utf-8") as f: + try: + f.write("\\documentclass[a4paper]{article}\n") + f.write("\\usepackage[a4paper,margin=1.5cm]{geometry}\n") + f.write("\\usepackage[T1]{fontenc}\n") + f.write("\\usepackage{color}\n") + f.write("\\usepackage{alltt}\n") + f.write("\\usepackage{times}\n") + if self.editor.isUtf8(): + f.write("\\usepackage[utf8]{inputenc}\n") + else: + f.write("\\usepackage[latin1]{inputenc}\n") + + if lex: + istyle = 0 + while istyle <= QsciScintilla.STYLE_MAX: + if ( + (istyle <= QsciScintilla.STYLE_DEFAULT or + istyle > QsciScintilla.STYLE_LASTPREDEFINED) and + styleIsUsed[istyle] and + (lex.description(istyle) or + istyle == QsciScintilla.STYLE_DEFAULT) + ): + font = lex.font(istyle) + colour = lex.color(istyle) + paper = lex.paper(istyle) + + self.__defineTexStyle( + font, colour, paper, f, istyle) + # get substyles + subs_start, subs_count = ( + self.editor.getSubStyleRange(istyle) + ) + for subs_idx in range(subs_count): + font = lex.font(subs_start + subs_idx) + colour = lex.color( + subs_start + subs_idx) + paper = lex.paper( + subs_start + subs_idx) + + self.__defineTexStyle( + font, colour, paper, f, + subs_idx - subs_start) + + istyle += 1 + else: + colour = self.editor.color() + paper = self.editor.paper() + font = Preferences.getEditorOtherFonts("DefaultFont") + + self.__defineTexStyle(font, colour, paper, f, 0) + self.__defineTexStyle(font, colour, paper, f, + QsciScintilla.STYLE_DEFAULT) + + f.write("\\begin{document}\n\n") + title = ( + self.editor.getFileName() + if titleFullPath else + os.path.basename(self.editor.getFileName()) + ) + f.write( + "Source File: {0}\n\n\\noindent\n\\tiny{{\n" + .format(title)) + + styleCurrent = self.editor.styleAt(0) + f.write("\\eric{0}{{" + .format(self.__texStyle(styleCurrent))) + + lineIdx = 0 + pos = 0 + utf8 = self.editor.isUtf8() + utf8Ch = b"" + utf8Len = 0 + + while pos < lengthDoc: + ch = self.editor.byteAt(pos) + style = self.editor.styleAt(pos) + if style != styleCurrent: + # new style + f.write( + "}}\n\\eric{0}{{".format( + self.__texStyle(style))) + styleCurrent = style + + if ch == b'\t': + ts = tabSize - (lineIdx % tabSize) + lineIdx += ts - 1 + f.write("\\hspace*{{{0:d}em}}".format(ts)) + elif ch == b'\\': + f.write("{\\textbackslash}") + elif ch in [b'>', b'<', b'@']: + f.write("${0}$".format(ch[0])) + elif ch in [b'{', b'}', b'^', b'_', b'&', b'$', b'#', + b'%', b'~']: + f.write("\\{0}".format(ch[0])) + elif ch in [b'\r', b'\n']: + lineIdx = -1 # because incremented below + if ( + ch == b'\r' and + self.editor.byteAt(pos + 1) == b'\n' + ): + pos += 1 # skip the LF + styleCurrent = self.editor.styleAt(pos + 1) + f.write("}} \\\\\n\\eric{0}{{".format( + self.__texStyle(styleCurrent))) + elif ch == b' ': + if self.editor.byteAt(pos + 1) == b' ': + f.write("{\\hspace*{1em}}") + else: + f.write(' ') + else: + if ord(ch) > 127 and utf8: + utf8Ch += ch + if utf8Len == 0: + if (utf8Ch[0] & 0xF0) == 0xF0: + utf8Len = 4 + elif (utf8Ch[0] & 0xE0) == 0xE0: + utf8Len = 3 + elif (utf8Ch[0] & 0xC0) == 0xC0: + utf8Len = 2 + elif len(utf8Ch) == utf8Len: + ch = utf8Ch.decode('utf8') + f.write(ch) + utf8Ch = b"" + utf8Len = 0 + else: + f.write(ch.decode()) + lineIdx += 1 + pos += 1 + + # close last empty style macros and document too + f.write("}\n} %end tiny\n\n\\end{document}\n") + except OSError as err: + E5MessageBox.critical( + self.editor, + self.tr("Export source"), + self.tr( + """<p>The source could not be exported to""" + """ <b>{0}</b>.</p><p>Reason: {1}</p>""") + .format(filename, str(err)))