QScintilla/Exporters/ExporterRTF.py

Sun, 31 Dec 2017 16:52:09 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 31 Dec 2017 16:52:09 +0100
changeset 6048
82ad8ec9548c
parent 5389
9b1c800daff3
child 6105
cbd34d558bd9
permissions
-rw-r--r--

Updated copyright for 2018.

# -*- coding: utf-8 -*-

# Copyright (c) 2007 - 2018 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing an exporter for RTF.
"""

from __future__ import unicode_literals

# 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.QtCore import Qt
from PyQt5.QtGui import QCursor, QFontInfo
from PyQt5.QtWidgets import QApplication
from PyQt5.Qsci import QsciScintilla

from E5Gui import E5MessageBox

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 = "{\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
        
        try:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            QApplication.processEvents()
            
            self.editor.recolor(0, -1)
            lex = self.editor.getLexer()
            
            tabSize = Preferences.getEditor("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:
                f = open(filename, "w", encoding="utf-8")
                
                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)
                f.close()
            except IOError as err:
                QApplication.restoreOverrideCursor()
                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)))
        finally:
            QApplication.restoreOverrideCursor()

eric ide

mercurial