--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/QScintilla/Exporters/ExporterRTF.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,455 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an exporter for RTF. +""" + +# 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 time + +from PyQt5.QtGui import QFontInfo +from PyQt5.Qsci import QsciScintilla + +from E5Gui import E5MessageBox +from E5Gui.E5OverrideCursor import E5OverrideCursor + +from .ExporterBase import ExporterBase + +import Preferences + + +class ExporterRTF(ExporterBase): + """ + Class implementing an exporter for RTF. + """ + RTF_HEADEROPEN = "{\\rtf1\\ansi\\deff0\\deftab720" + RTF_HEADERCLOSE = "\n" + RTF_FONTDEFOPEN = "{\\fonttbl" + RTF_FONTDEF = "{{\\f{0:d}\\fnil\\fcharset{1:d} {2};}}" + RTF_FONTDEFCLOSE = "}" + RTF_COLORDEFOPEN = "{\\colortbl" + RTF_COLORDEF = "\\red{0:d}\\green{1:d}\\blue{2:d};" + RTF_COLORDEFCLOSE = "}" + RTF_INFOOPEN = "{\\info " + RTF_INFOCLOSE = "}" + RTF_COMMENT = "{\\comment Generated by eric's RTF export filter.}" + # to be used by strftime + RTF_CREATED = r"{\creatim\yr%Y\mo%m\dy%d\hr%H\min%M\sec%S}" + RTF_BODYOPEN = "" + RTF_BODYCLOSE = "}" + + RTF_SETFONTFACE = "\\f" + RTF_SETFONTSIZE = "\\fs" + RTF_SETCOLOR = "\\cf" + RTF_SETBACKGROUND = "\\highlight" + RTF_BOLD_ON = "\\b" + RTF_BOLD_OFF = "\\b0" + RTF_ITALIC_ON = "\\i" + RTF_ITALIC_OFF = "\\i0" + + RTF_EOLN = "\\line\n" + RTF_TAB = "\\tab " + + RTF_COLOR = "#000000" + + 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 __GetRTFNextControl(self, pos, style): + """ + Private method to extract the next RTF control word from style. + + @param pos position to start search (integer) + @param style style definition to search in (string) + @return tuple of new start position and control word found + (integer, string) + """ + # \f0\fs20\cf0\highlight0\b0\i0 + if pos >= len(style): + return pos, "" + + oldpos = pos + pos += 1 # implicit skip over leading '\' + while pos < len(style) and style[pos] != '\\': + pos += 1 + return pos, style[oldpos:pos] + + def __GetRTFStyleChange(self, last, current): + """ + Private method to extract control words that are different between two + styles. + + @param last least recently used style (string) + @param current current style (string) + @return string containing the delta between these styles (string) + """ + # \f0\fs20\cf0\highlight0\b0\i0 + lastPos = 0 + currentPos = 0 + delta = '' + i = 0 + while i < 6: + lastPos, lastControl = self.__GetRTFNextControl(lastPos, last) + currentPos, currentControl = self.__GetRTFNextControl(currentPos, + current) + if lastControl != currentControl: + delta += currentControl + i += 1 + if delta != '': + delta += ' ' + return delta + + def exportSource(self): + """ + Public method performing the export. + """ + filename = self._getFileName(self.tr("RTF Files (*.rtf)")) + if not filename: + return + + self.editor.recolor(0, -1) + tabs = Preferences.getEditorExporter("RTF/UseTabs") + tabSize = self.editor.getEditorConfig("TabWidth") + if tabSize == 0: + tabSize = 4 + + with E5OverrideCursor(), open(filename, "w", encoding="utf-8") as f: + try: + styles, fontsize = self.__prepareStyles(f) + + lastStyle = ( + self.RTF_SETFONTFACE + "0" + + self.RTF_SETFONTSIZE + "{0:d}".format(fontsize) + + self.RTF_SETCOLOR + "0" + + self.RTF_SETBACKGROUND + "1" + + self.RTF_BOLD_OFF + + self.RTF_ITALIC_OFF + ) + + lengthDoc = self.editor.length() + prevCR = False + column = 0 + pos = 0 + deltaStyle = "" + styleCurrent = -1 + utf8 = self.editor.isUtf8() + utf8Ch = b"" + utf8Len = 0 + + while pos < lengthDoc: + ch = self.editor.byteAt(pos) + style = self.editor.styleAt(pos) + if style != styleCurrent: + deltaStyle = self.__GetRTFStyleChange( + lastStyle, styles[style]) + if deltaStyle: + f.write(deltaStyle) + styleCurrent = style + lastStyle = styles[style] + + if ch == b'{': + f.write('\\{') + elif ch == b'}': + f.write('\\}') + elif ch == b'\\': + f.write('\\\\') + elif ch == b'\t': + if tabs: + f.write(self.RTF_TAB) + else: + ts = tabSize - (column % tabSize) + f.write(' ' * ts) + column += ts - 1 + elif ch == b'\n': + if not prevCR: + f.write(self.RTF_EOLN) + column -= 1 + elif ch == b'\r': + f.write(self.RTF_EOLN) + column -= 1 + else: + if ord(ch) > 0x7F 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 + column -= 1 + # will be incremented again later + elif len(utf8Ch) == utf8Len: + ch = utf8Ch.decode('utf8') + if ord(ch) <= 0xff: + f.write("\\'{0:x}".format(ord(ch))) + else: + f.write("\\u{0:d}\\'{1:x}".format( + ord(ch), ord(ch) & 0xFF)) + utf8Ch = b"" + utf8Len = 0 + else: + column -= 1 + # will be incremented again later + else: + f.write(ch.decode()) + + column += 1 + prevCR = ch == b'\r' + pos += 1 + + f.write(self.RTF_BODYCLOSE) + 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))) + + def __prepareStyles(self, f): + """ + Private method to generate and store the different styles. + + @param f filepointer to the open RTF + @type object + @return styles, fontsize + @rtype dict, int + """ + styles = {} + fonts = {} + colors = {} + lastStyle = "" + + lex = self.editor.getLexer() + + wysiwyg = Preferences.getEditorExporter("RTF/WYSIWYG") + if wysiwyg: + if lex: + defaultFont = lex.font(QsciScintilla.STYLE_DEFAULT) + else: + defaultFont = Preferences.getEditorOtherFonts("DefaultFont") + else: + defaultFont = Preferences.getEditorExporter("RTF/Font") + fontface = defaultFont.family() + fontsize = QFontInfo(defaultFont).pointSize() << 1 + if fontsize == 0: + fontsize = 10 << 1 + characterset = QsciScintilla.SC_CHARSET_DEFAULT + + if lex: + fgColour = lex.color(QsciScintilla.STYLE_DEFAULT) + bgColour = lex.paper(QsciScintilla.STYLE_DEFAULT) + else: + fgColour = self.editor.color() + bgColour = self.editor.paper() + + f.write(self.RTF_HEADEROPEN + self.RTF_FONTDEFOPEN) + fonts[0] = fontface + fontCount = 1 + f.write(self.RTF_FONTDEF.format(0, characterset, fontface)) + colors[0] = fgColour + colors[1] = bgColour + colorCount = 2 + + if lex: + istyle = 0 + while istyle <= QsciScintilla.STYLE_MAX: + if ( + istyle < QsciScintilla.STYLE_DEFAULT or + istyle > QsciScintilla.STYLE_LASTPREDEFINED + ): + if lex.description(istyle): + font = lex.font(istyle) + lastStyle = self.RTF_SETFONTFACE + if wysiwyg: + fontKey = None + for key, value in fonts.items(): + if value.lower() == font.family().lower(): + fontKey = key + break + else: + fonts[fontCount] = font.family() + f.write(self.RTF_FONTDEF.format( + fontCount, characterset, + font.family())) + fontKey = fontCount + fontCount += 1 + + lastStyle += "{0:d}".format(fontKey) + else: + lastStyle += "0" + + lastStyle += self.RTF_SETFONTSIZE + if wysiwyg and QFontInfo(font).pointSize(): + lastStyle += ( + "{0:d}".format( + QFontInfo(font).pointSize() << 1) + ) + else: + lastStyle += "{0:d}".format(fontsize) + + sColour = lex.color(istyle) + sColourKey = None + for key, value in colors.items(): + if value == sColour: + sColourKey = key + break + else: + colors[colorCount] = sColour + sColourKey = colorCount + colorCount += 1 + lastStyle += ( + self.RTF_SETCOLOR + + "{0:d}".format(sColourKey) + ) + + sColour = lex.paper(istyle) + sColourKey = None + for key, value in colors.items(): + if value == sColour: + sColourKey = key + break + else: + colors[colorCount] = sColour + sColourKey = colorCount + colorCount += 1 + + lastStyle += ( + self.RTF_SETBACKGROUND + + "{0:d}".format(sColourKey) + ) + + if font.bold(): + lastStyle += self.RTF_BOLD_ON + else: + lastStyle += self.RTF_BOLD_OFF + if font.italic(): + lastStyle += self.RTF_ITALIC_ON + else: + lastStyle += self.RTF_ITALIC_OFF + styles[istyle] = lastStyle + + # get substyles + subs_start, subs_count = self.editor.getSubStyleRange( + istyle) + for subs_idx in range(subs_count): + font = lex.font(subs_start + subs_idx) + lastStyle = self.RTF_SETFONTFACE + if wysiwyg: + fontKey = None + for key, value in fonts.items(): + if value.lower() == font.family().lower(): + fontKey = key + break + else: + fonts[fontCount] = font.family() + f.write(self.RTF_FONTDEF.format( + fontCount, characterset, + font.family())) + fontKey = fontCount + fontCount += 1 + + lastStyle += "{0:d}".format(fontKey) + else: + lastStyle += "0" + + lastStyle += self.RTF_SETFONTSIZE + if wysiwyg and QFontInfo(font).pointSize(): + lastStyle += ( + "{0:d}".format( + QFontInfo(font).pointSize() << 1) + ) + else: + lastStyle += "{0:d}".format(fontsize) + + sColour = lex.color(subs_start + subs_idx) + sColourKey = None + for key, value in colors.items(): + if value == sColour: + sColourKey = key + break + else: + colors[colorCount] = sColour + sColourKey = colorCount + colorCount += 1 + lastStyle += ( + self.RTF_SETCOLOR + + "{0:d}".format(sColourKey) + ) + + sColour = lex.paper(subs_start + subs_idx) + sColourKey = None + for key, value in colors.items(): + if value == sColour: + sColourKey = key + break + else: + colors[colorCount] = sColour + sColourKey = colorCount + colorCount += 1 + + lastStyle += ( + self.RTF_SETBACKGROUND + + "{0:d}".format(sColourKey) + ) + + if font.bold(): + lastStyle += self.RTF_BOLD_ON + else: + lastStyle += self.RTF_BOLD_OFF + if font.italic(): + lastStyle += self.RTF_ITALIC_ON + else: + lastStyle += self.RTF_ITALIC_OFF + styles[subs_idx - subs_start] = lastStyle + + else: + styles[istyle] = ( + self.RTF_SETFONTFACE + "0" + + self.RTF_SETFONTSIZE + + "{0:d}".format(fontsize) + + self.RTF_SETCOLOR + "0" + + self.RTF_SETBACKGROUND + "1" + + self.RTF_BOLD_OFF + + self.RTF_ITALIC_OFF + ) + + istyle += 1 + else: + styles[0] = ( + self.RTF_SETFONTFACE + "0" + + self.RTF_SETFONTSIZE + + "{0:d}".format(fontsize) + + self.RTF_SETCOLOR + "0" + + self.RTF_SETBACKGROUND + "1" + + self.RTF_BOLD_OFF + + self.RTF_ITALIC_OFF + ) + + f.write(self.RTF_FONTDEFCLOSE + self.RTF_COLORDEFOPEN) + for value in colors.values(): + f.write(self.RTF_COLORDEF.format( + value.red(), value.green(), value.blue())) + f.write(self.RTF_COLORDEFCLOSE) + f.write(self.RTF_INFOOPEN + self.RTF_COMMENT) + f.write(time.strftime(self.RTF_CREATED)) + f.write(self.RTF_INFOCLOSE) + f.write(self.RTF_HEADERCLOSE + + self.RTF_BODYOPEN + self.RTF_SETFONTFACE + "0" + + self.RTF_SETFONTSIZE + "{0:d}".format(fontsize) + + self.RTF_SETCOLOR + "0 ") + + return styles, fontsize