src/eric7/QScintilla/Exporters/ExporterRTF.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing an exporter for RTF.
8 """
9
10 # This code is a port of the C++ code found in SciTE 1.74
11 # Original code: Copyright 1998-2006 by Neil Hodgson <neilh@scintilla.org>
12
13 import time
14
15 from PyQt6.QtGui import QFontInfo
16 from PyQt6.Qsci import QsciScintilla
17
18 from EricWidgets import EricMessageBox
19 from EricGui.EricOverrideCursor import EricOverrideCursor
20
21 from .ExporterBase import ExporterBase
22
23 import Preferences
24
25
26 class ExporterRTF(ExporterBase):
27 """
28 Class implementing an exporter for RTF.
29 """
30 RTF_HEADEROPEN = "{\\rtf1\\ansi\\deff0\\deftab720"
31 RTF_HEADERCLOSE = "\n"
32 RTF_FONTDEFOPEN = "{\\fonttbl"
33 RTF_FONTDEF = "{{\\f{0:d}\\fnil\\fcharset{1:d} {2};}}"
34 RTF_FONTDEFCLOSE = "}"
35 RTF_COLORDEFOPEN = "{\\colortbl"
36 RTF_COLORDEF = "\\red{0:d}\\green{1:d}\\blue{2:d};"
37 RTF_COLORDEFCLOSE = "}"
38 RTF_INFOOPEN = "{\\info "
39 RTF_INFOCLOSE = "}"
40 RTF_COMMENT = "{\\comment Generated by eric's RTF export filter.}"
41 # to be used by strftime
42 RTF_CREATED = r"{\creatim\yr%Y\mo%m\dy%d\hr%H\min%M\sec%S}"
43 RTF_BODYOPEN = ""
44 RTF_BODYCLOSE = "}"
45
46 RTF_SETFONTFACE = "\\f"
47 RTF_SETFONTSIZE = "\\fs"
48 RTF_SETCOLOR = "\\cf"
49 RTF_SETBACKGROUND = "\\highlight"
50 RTF_BOLD_ON = "\\b"
51 RTF_BOLD_OFF = "\\b0"
52 RTF_ITALIC_ON = "\\i"
53 RTF_ITALIC_OFF = "\\i0"
54
55 RTF_EOLN = "\\line\n"
56 RTF_TAB = "\\tab "
57
58 RTF_COLOR = "#000000"
59
60 def __init__(self, editor, parent=None):
61 """
62 Constructor
63
64 @param editor reference to the editor object (QScintilla.Editor.Editor)
65 @param parent parent object of the exporter (QObject)
66 """
67 ExporterBase.__init__(self, editor, parent)
68
69 def __GetRTFNextControl(self, pos, style):
70 """
71 Private method to extract the next RTF control word from style.
72
73 @param pos position to start search (integer)
74 @param style style definition to search in (string)
75 @return tuple of new start position and control word found
76 (integer, string)
77 """
78 # \f0\fs20\cf0\highlight0\b0\i0
79 if pos >= len(style):
80 return pos, ""
81
82 oldpos = pos
83 pos += 1 # implicit skip over leading '\'
84 while pos < len(style) and style[pos] != '\\':
85 pos += 1
86 return pos, style[oldpos:pos]
87
88 def __GetRTFStyleChange(self, last, current):
89 """
90 Private method to extract control words that are different between two
91 styles.
92
93 @param last least recently used style (string)
94 @param current current style (string)
95 @return string containing the delta between these styles (string)
96 """
97 # \f0\fs20\cf0\highlight0\b0\i0
98 lastPos = 0
99 currentPos = 0
100 delta = ''
101 i = 0
102 while i < 6:
103 lastPos, lastControl = self.__GetRTFNextControl(lastPos, last)
104 currentPos, currentControl = self.__GetRTFNextControl(currentPos,
105 current)
106 if lastControl != currentControl:
107 delta += currentControl
108 i += 1
109 if delta != '':
110 delta += ' '
111 return delta
112
113 def exportSource(self):
114 """
115 Public method performing the export.
116 """
117 filename = self._getFileName(self.tr("RTF Files (*.rtf)"))
118 if not filename:
119 return
120
121 self.editor.recolor(0, -1)
122 tabs = Preferences.getEditorExporter("RTF/UseTabs")
123 tabSize = self.editor.getEditorConfig("TabWidth")
124 if tabSize == 0:
125 tabSize = 4
126
127 with EricOverrideCursor(), open(filename, "w", encoding="utf-8") as f:
128 try:
129 styles, fontsize = self.__prepareStyles(f)
130
131 lastStyle = (
132 self.RTF_SETFONTFACE + "0" +
133 self.RTF_SETFONTSIZE + "{0:d}".format(fontsize) +
134 self.RTF_SETCOLOR + "0" +
135 self.RTF_SETBACKGROUND + "1" +
136 self.RTF_BOLD_OFF +
137 self.RTF_ITALIC_OFF
138 )
139
140 lengthDoc = self.editor.length()
141 prevCR = False
142 column = 0
143 pos = 0
144 deltaStyle = ""
145 styleCurrent = -1
146 utf8 = self.editor.isUtf8()
147 utf8Ch = b""
148 utf8Len = 0
149
150 while pos < lengthDoc:
151 ch = self.editor.byteAt(pos)
152 style = self.editor.styleAt(pos)
153 if style != styleCurrent:
154 deltaStyle = self.__GetRTFStyleChange(
155 lastStyle, styles[style])
156 if deltaStyle:
157 f.write(deltaStyle)
158 styleCurrent = style
159 lastStyle = styles[style]
160
161 if ch == b'{':
162 f.write('\\{')
163 elif ch == b'}':
164 f.write('\\}')
165 elif ch == b'\\':
166 f.write('\\\\')
167 elif ch == b'\t':
168 if tabs:
169 f.write(self.RTF_TAB)
170 else:
171 ts = tabSize - (column % tabSize)
172 f.write(' ' * ts)
173 column += ts - 1
174 elif ch == b'\n':
175 if not prevCR:
176 f.write(self.RTF_EOLN)
177 column -= 1
178 elif ch == b'\r':
179 f.write(self.RTF_EOLN)
180 column -= 1
181 else:
182 if ord(ch) > 0x7F and utf8:
183 utf8Ch += ch
184 if utf8Len == 0:
185 if (utf8Ch[0] & 0xF0) == 0xF0:
186 utf8Len = 4
187 elif (utf8Ch[0] & 0xE0) == 0xE0:
188 utf8Len = 3
189 elif (utf8Ch[0] & 0xC0) == 0xC0:
190 utf8Len = 2
191 column -= 1
192 # will be incremented again later
193 elif len(utf8Ch) == utf8Len:
194 ch = utf8Ch.decode('utf8')
195 if ord(ch) <= 0xff:
196 f.write("\\'{0:x}".format(ord(ch)))
197 else:
198 f.write("\\u{0:d}\\'{1:x}".format(
199 ord(ch), ord(ch) & 0xFF))
200 utf8Ch = b""
201 utf8Len = 0
202 else:
203 column -= 1
204 # will be incremented again later
205 else:
206 f.write(ch.decode())
207
208 column += 1
209 prevCR = ch == b'\r'
210 pos += 1
211
212 f.write(self.RTF_BODYCLOSE)
213 except OSError as err:
214 EricMessageBox.critical(
215 self.editor,
216 self.tr("Export source"),
217 self.tr(
218 """<p>The source could not be exported to"""
219 """ <b>{0}</b>.</p><p>Reason: {1}</p>""")
220 .format(filename, str(err)))
221
222 def __prepareStyles(self, f):
223 """
224 Private method to generate and store the different styles.
225
226 @param f filepointer to the open RTF
227 @type object
228 @return styles, fontsize
229 @rtype dict, int
230 """
231 styles = {}
232 fonts = {}
233 colors = {}
234 lastStyle = ""
235
236 lex = self.editor.getLexer()
237
238 wysiwyg = Preferences.getEditorExporter("RTF/WYSIWYG")
239 if wysiwyg:
240 if lex:
241 defaultFont = lex.font(QsciScintilla.STYLE_DEFAULT)
242 else:
243 defaultFont = Preferences.getEditorOtherFonts("DefaultFont")
244 else:
245 defaultFont = Preferences.getEditorExporter("RTF/Font")
246 fontface = defaultFont.family()
247 fontsize = QFontInfo(defaultFont).pointSize() << 1
248 if fontsize == 0:
249 fontsize = 10 << 1
250 characterset = QsciScintilla.SC_CHARSET_DEFAULT
251
252 if lex:
253 fgColour = lex.color(QsciScintilla.STYLE_DEFAULT)
254 bgColour = lex.paper(QsciScintilla.STYLE_DEFAULT)
255 else:
256 fgColour = self.editor.color()
257 bgColour = self.editor.paper()
258
259 f.write(self.RTF_HEADEROPEN + self.RTF_FONTDEFOPEN)
260 fonts[0] = fontface
261 fontCount = 1
262 f.write(self.RTF_FONTDEF.format(0, characterset, fontface))
263 colors[0] = fgColour
264 colors[1] = bgColour
265 colorCount = 2
266
267 if lex:
268 istyle = 0
269 while istyle <= QsciScintilla.STYLE_MAX:
270 if (
271 istyle < QsciScintilla.STYLE_DEFAULT or
272 istyle > QsciScintilla.STYLE_LASTPREDEFINED
273 ):
274 if lex.description(istyle):
275 font = lex.font(istyle)
276 lastStyle = self.RTF_SETFONTFACE
277 if wysiwyg:
278 fontKey = None
279 for key, value in fonts.items():
280 if value.lower() == font.family().lower():
281 fontKey = key
282 break
283 else:
284 fonts[fontCount] = font.family()
285 f.write(self.RTF_FONTDEF.format(
286 fontCount, characterset,
287 font.family()))
288 fontKey = fontCount
289 fontCount += 1
290
291 lastStyle += "{0:d}".format(fontKey)
292 else:
293 lastStyle += "0"
294
295 lastStyle += self.RTF_SETFONTSIZE
296 if wysiwyg and QFontInfo(font).pointSize():
297 lastStyle += (
298 "{0:d}".format(
299 QFontInfo(font).pointSize() << 1)
300 )
301 else:
302 lastStyle += "{0:d}".format(fontsize)
303
304 sColour = lex.color(istyle)
305 sColourKey = None
306 for key, value in colors.items():
307 if value == sColour:
308 sColourKey = key
309 break
310 else:
311 colors[colorCount] = sColour
312 sColourKey = colorCount
313 colorCount += 1
314 lastStyle += (
315 self.RTF_SETCOLOR +
316 "{0:d}".format(sColourKey)
317 )
318
319 sColour = lex.paper(istyle)
320 sColourKey = None
321 for key, value in colors.items():
322 if value == sColour:
323 sColourKey = key
324 break
325 else:
326 colors[colorCount] = sColour
327 sColourKey = colorCount
328 colorCount += 1
329
330 lastStyle += (
331 self.RTF_SETBACKGROUND +
332 "{0:d}".format(sColourKey)
333 )
334
335 if font.bold():
336 lastStyle += self.RTF_BOLD_ON
337 else:
338 lastStyle += self.RTF_BOLD_OFF
339 if font.italic():
340 lastStyle += self.RTF_ITALIC_ON
341 else:
342 lastStyle += self.RTF_ITALIC_OFF
343 styles[istyle] = lastStyle
344
345 # get substyles
346 subs_start, subs_count = self.editor.getSubStyleRange(
347 istyle)
348 for subs_idx in range(subs_count):
349 font = lex.font(subs_start + subs_idx)
350 lastStyle = self.RTF_SETFONTFACE
351 if wysiwyg:
352 fontKey = None
353 for key, value in fonts.items():
354 if value.lower() == font.family().lower():
355 fontKey = key
356 break
357 else:
358 fonts[fontCount] = font.family()
359 f.write(self.RTF_FONTDEF.format(
360 fontCount, characterset,
361 font.family()))
362 fontKey = fontCount
363 fontCount += 1
364
365 lastStyle += "{0:d}".format(fontKey)
366 else:
367 lastStyle += "0"
368
369 lastStyle += self.RTF_SETFONTSIZE
370 if wysiwyg and QFontInfo(font).pointSize():
371 lastStyle += (
372 "{0:d}".format(
373 QFontInfo(font).pointSize() << 1)
374 )
375 else:
376 lastStyle += "{0:d}".format(fontsize)
377
378 sColour = lex.color(subs_start + subs_idx)
379 sColourKey = None
380 for key, value in colors.items():
381 if value == sColour:
382 sColourKey = key
383 break
384 else:
385 colors[colorCount] = sColour
386 sColourKey = colorCount
387 colorCount += 1
388 lastStyle += (
389 self.RTF_SETCOLOR +
390 "{0:d}".format(sColourKey)
391 )
392
393 sColour = lex.paper(subs_start + subs_idx)
394 sColourKey = None
395 for key, value in colors.items():
396 if value == sColour:
397 sColourKey = key
398 break
399 else:
400 colors[colorCount] = sColour
401 sColourKey = colorCount
402 colorCount += 1
403
404 lastStyle += (
405 self.RTF_SETBACKGROUND +
406 "{0:d}".format(sColourKey)
407 )
408
409 if font.bold():
410 lastStyle += self.RTF_BOLD_ON
411 else:
412 lastStyle += self.RTF_BOLD_OFF
413 if font.italic():
414 lastStyle += self.RTF_ITALIC_ON
415 else:
416 lastStyle += self.RTF_ITALIC_OFF
417 styles[subs_idx - subs_start] = lastStyle
418
419 else:
420 styles[istyle] = (
421 self.RTF_SETFONTFACE + "0" +
422 self.RTF_SETFONTSIZE +
423 "{0:d}".format(fontsize) +
424 self.RTF_SETCOLOR + "0" +
425 self.RTF_SETBACKGROUND + "1" +
426 self.RTF_BOLD_OFF +
427 self.RTF_ITALIC_OFF
428 )
429
430 istyle += 1
431 else:
432 styles[0] = (
433 self.RTF_SETFONTFACE + "0" +
434 self.RTF_SETFONTSIZE +
435 "{0:d}".format(fontsize) +
436 self.RTF_SETCOLOR + "0" +
437 self.RTF_SETBACKGROUND + "1" +
438 self.RTF_BOLD_OFF +
439 self.RTF_ITALIC_OFF
440 )
441
442 f.write(self.RTF_FONTDEFCLOSE + self.RTF_COLORDEFOPEN)
443 for value in colors.values():
444 f.write(self.RTF_COLORDEF.format(
445 value.red(), value.green(), value.blue()))
446 f.write(self.RTF_COLORDEFCLOSE)
447 f.write(self.RTF_INFOOPEN + self.RTF_COMMENT)
448 f.write(time.strftime(self.RTF_CREATED))
449 f.write(self.RTF_INFOCLOSE)
450 f.write(self.RTF_HEADERCLOSE +
451 self.RTF_BODYOPEN + self.RTF_SETFONTFACE + "0" +
452 self.RTF_SETFONTSIZE + "{0:d}".format(fontsize) +
453 self.RTF_SETCOLOR + "0 ")
454
455 return styles, fontsize

eric ide

mercurial