eric6/QScintilla/Exporters/ExporterRTF.py

Wed, 30 Dec 2020 11:00:44 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 30 Dec 2020 11:00:44 +0100
branch
maintenance
changeset 7924
8a96736d465e
parent 7836
2f0d208b8137
child 7949
17c0a4ec3cf0
permissions
-rw-r--r--

Updated copyright for 2021.

# -*- 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 eric6'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)
        lex = self.editor.getLexer()
        
        tabSize = self.editor.getEditorConfig("TabWidth")
        if tabSize == 0:
            tabSize = 4
        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
        tabs = Preferences.getEditorExporter("RTF/UseTabs")
        
        if lex:
            fgColour = lex.color(QsciScintilla.STYLE_DEFAULT)
            bgColour = lex.paper(QsciScintilla.STYLE_DEFAULT)
        else:
            fgColour = self.editor.color()
            bgColour = self.editor.paper()
        
        try:
            with E5OverrideCursor():
                with open(filename, "w", encoding="utf-8") as f:
                    
                    styles = {}
                    fonts = {}
                    colors = {}
                    lastStyle = ""
                    
                    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)
                                    if wysiwyg:
                                        fontKey = None
                                        for key, value in list(fonts.items()):
                                            if (
                                                value.lower() ==
                                                    font.family().lower()
                                            ):
                                                fontKey = key
                                                break
                                        if fontKey is None:
                                            fonts[fontCount] = font.family()
                                            f.write(self.RTF_FONTDEF.format(
                                                fontCount, characterset,
                                                font.family()))
                                            fontKey = fontCount
                                            fontCount += 1
                                        lastStyle = (
                                            self.RTF_SETFONTFACE +
                                            "{0:d}".format(fontKey)
                                        )
                                    else:
                                        lastStyle = self.RTF_SETFONTFACE + "0"
                                    
                                    if wysiwyg and QFontInfo(font).pointSize():
                                        lastStyle += (
                                            self.RTF_SETFONTSIZE +
                                            "{0:d}".format(
                                                QFontInfo(font).pointSize() <<
                                                1)
                                        )
                                    else:
                                        lastStyle += (
                                            self.RTF_SETFONTSIZE +
                                            "{0:d}".format(fontsize)
                                        )
                                    
                                    sColour = lex.color(istyle)
                                    sColourKey = None
                                    for key, value in list(colors.items()):
                                        if value == sColour:
                                            sColourKey = key
                                            break
                                    if sColourKey is None:
                                        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 list(colors.items()):
                                        if value == sColour:
                                            sColourKey = key
                                            break
                                    if sColourKey is None:
                                        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
                                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 list(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 ")
                    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)))

eric ide

mercurial