novelWriter 2.4.4__py3-none-any.whl → 2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
- {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/RECORD +121 -111
- {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_pl_PL.json +116 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +52 -41
- novelwriter/assets/themes/cyberpunk_night.conf +3 -0
- novelwriter/assets/themes/default_dark.conf +2 -0
- novelwriter/assets/themes/default_light.conf +2 -0
- novelwriter/assets/themes/dracula.conf +48 -0
- novelwriter/assets/themes/solarized_dark.conf +2 -0
- novelwriter/assets/themes/solarized_light.conf +2 -0
- novelwriter/common.py +33 -12
- novelwriter/config.py +184 -98
- novelwriter/constants.py +47 -35
- novelwriter/core/buildsettings.py +68 -69
- novelwriter/core/coretools.py +5 -23
- novelwriter/core/docbuild.py +52 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +37 -61
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +139 -105
- novelwriter/core/tokenizer.py +278 -122
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +257 -166
- novelwriter/core/toqdoc.py +419 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +11 -18
- novelwriter/dialogs/docmerge.py +17 -19
- novelwriter/dialogs/docsplit.py +17 -19
- novelwriter/dialogs/editlabel.py +6 -10
- novelwriter/dialogs/preferences.py +200 -164
- novelwriter/dialogs/projectsettings.py +225 -189
- novelwriter/dialogs/quotes.py +12 -9
- novelwriter/dialogs/wordlist.py +9 -15
- novelwriter/enum.py +35 -30
- novelwriter/error.py +8 -15
- novelwriter/extensions/configlayout.py +55 -21
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +58 -14
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
- novelwriter/extensions/statusled.py +40 -26
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +120 -139
- novelwriter/gui/dochighlight.py +231 -186
- novelwriter/gui/docviewer.py +69 -108
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +22 -18
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +15 -26
- novelwriter/gui/projtree.py +35 -60
- novelwriter/gui/search.py +10 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +29 -37
- novelwriter/gui/theme.py +26 -48
- novelwriter/guimain.py +162 -160
- novelwriter/shared.py +36 -32
- novelwriter/text/patterns.py +113 -0
- novelwriter/tools/dictionaries.py +10 -20
- novelwriter/tools/lipsum.py +10 -16
- novelwriter/tools/manusbuild.py +9 -11
- novelwriter/tools/manuscript.py +71 -145
- novelwriter/tools/manussettings.py +71 -75
- novelwriter/tools/noveldetails.py +16 -21
- novelwriter/tools/welcome.py +21 -26
- novelwriter/tools/writingstats.py +9 -12
- novelwriter/types.py +49 -4
- novelwriter/extensions/simpleprogress.py +0 -55
- {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.4.dist-info → novelWriter-2.5.dist-info}/top_level.txt +0 -0
novelwriter/core/toodt.py
CHANGED
@@ -29,17 +29,20 @@ from __future__ import annotations
|
|
29
29
|
import logging
|
30
30
|
import xml.etree.ElementTree as ET
|
31
31
|
|
32
|
+
from collections.abc import Sequence
|
33
|
+
from datetime import datetime
|
32
34
|
from hashlib import sha256
|
33
35
|
from pathlib import Path
|
34
36
|
from zipfile import ZipFile
|
35
|
-
|
36
|
-
from
|
37
|
+
|
38
|
+
from PyQt5.QtGui import QFont
|
37
39
|
|
38
40
|
from novelwriter import __version__
|
39
41
|
from novelwriter.common import xmlIndent
|
40
42
|
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
|
41
43
|
from novelwriter.core.project import NWProject
|
42
|
-
from novelwriter.core.tokenizer import Tokenizer, stripEscape
|
44
|
+
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
|
45
|
+
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
|
43
46
|
|
44
47
|
logger = logging.getLogger(__name__)
|
45
48
|
|
@@ -79,13 +82,15 @@ TAG_SPAN = _mkTag("text", "span")
|
|
79
82
|
TAG_STNM = _mkTag("text", "style-name")
|
80
83
|
|
81
84
|
# Formatting Codes
|
82
|
-
X_BLD =
|
83
|
-
X_ITA =
|
84
|
-
X_DEL =
|
85
|
-
X_UND =
|
86
|
-
X_MRK =
|
87
|
-
X_SUP =
|
88
|
-
X_SUB =
|
85
|
+
X_BLD = 0x001 # Bold format
|
86
|
+
X_ITA = 0x002 # Italic format
|
87
|
+
X_DEL = 0x004 # Strikethrough format
|
88
|
+
X_UND = 0x008 # Underline format
|
89
|
+
X_MRK = 0x010 # Marked format
|
90
|
+
X_SUP = 0x020 # Superscript
|
91
|
+
X_SUB = 0x040 # Subscript
|
92
|
+
X_DLG = 0x080 # Dialogue
|
93
|
+
X_DLA = 0x100 # Alt. Dialogue
|
89
94
|
|
90
95
|
# Formatting Masks
|
91
96
|
M_BLD = ~X_BLD
|
@@ -95,6 +100,24 @@ M_UND = ~X_UND
|
|
95
100
|
M_MRK = ~X_MRK
|
96
101
|
M_SUP = ~X_SUP
|
97
102
|
M_SUB = ~X_SUB
|
103
|
+
M_DLG = ~X_DLG
|
104
|
+
M_DLA = ~X_DLA
|
105
|
+
|
106
|
+
# ODT Styles
|
107
|
+
S_TITLE = "Title"
|
108
|
+
S_HEAD1 = "Heading_20_1"
|
109
|
+
S_HEAD2 = "Heading_20_2"
|
110
|
+
S_HEAD3 = "Heading_20_3"
|
111
|
+
S_HEAD4 = "Heading_20_4"
|
112
|
+
S_SEP = "Separator"
|
113
|
+
S_FIND = "First_20_line_20_indent"
|
114
|
+
S_TEXT = "Text_20_body"
|
115
|
+
S_META = "Text_20_Meta"
|
116
|
+
S_HNF = "Header_20_and_20_Footer"
|
117
|
+
|
118
|
+
# Font Data
|
119
|
+
FONT_WEIGHT_NUM = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]
|
120
|
+
FONT_WEIGHT_MAP = {"400": "normal", "700": "bold"}
|
98
121
|
|
99
122
|
|
100
123
|
class ToOdt(Tokenizer):
|
@@ -130,20 +153,25 @@ class ToOdt(Tokenizer):
|
|
130
153
|
self._autoPara: dict[str, ODTParagraphStyle] = {} # Auto-generated paragraph styles
|
131
154
|
self._autoText: dict[int, ODTTextStyle] = {} # Auto-generated text styles
|
132
155
|
|
156
|
+
# Footnotes
|
157
|
+
self._nNote = 0
|
158
|
+
self._etNotes: dict[str, ET.Element] = {} # Generated note elements
|
159
|
+
|
133
160
|
self._errData = [] # List of errors encountered
|
134
161
|
|
135
162
|
# Properties
|
136
|
-
self._textFont = "Liberation Serif"
|
137
|
-
self._textSize = 12
|
138
|
-
self._textFixed = False
|
163
|
+
self._textFont = QFont("Liberation Serif", 12)
|
139
164
|
self._colourHead = False
|
140
|
-
self._firstIndent = False
|
141
165
|
self._headerFormat = ""
|
142
166
|
self._pageOffset = 0
|
143
167
|
|
144
168
|
# Internal
|
145
|
-
self._fontFamily = "
|
169
|
+
self._fontFamily = "Liberation Serif"
|
170
|
+
self._fontSize = 12
|
171
|
+
self._fontWeight = "normal"
|
172
|
+
self._fontStyle = "normal"
|
146
173
|
self._fontPitch = "variable"
|
174
|
+
self._fontBold = "bold"
|
147
175
|
self._fSizeTitle = "30pt"
|
148
176
|
self._fSizeHead1 = "24pt"
|
149
177
|
self._fSizeHead2 = "20pt"
|
@@ -151,6 +179,7 @@ class ToOdt(Tokenizer):
|
|
151
179
|
self._fSizeHead4 = "14pt"
|
152
180
|
self._fSizeHead = "14pt"
|
153
181
|
self._fSizeText = "12pt"
|
182
|
+
self._fSizeFoot = "10pt"
|
154
183
|
self._fLineHeight = "115%"
|
155
184
|
self._fBlockIndent = "1.693cm"
|
156
185
|
self._fTextIndent = "0.499cm"
|
@@ -167,6 +196,7 @@ class ToOdt(Tokenizer):
|
|
167
196
|
self._mTopHead = "0.423cm"
|
168
197
|
self._mTopText = "0.000cm"
|
169
198
|
self._mTopMeta = "0.000cm"
|
199
|
+
self._mTopSep = "0.247cm"
|
170
200
|
|
171
201
|
self._mBotTitle = "0.212cm"
|
172
202
|
self._mBotHead1 = "0.212cm"
|
@@ -176,6 +206,10 @@ class ToOdt(Tokenizer):
|
|
176
206
|
self._mBotHead = "0.212cm"
|
177
207
|
self._mBotText = "0.247cm"
|
178
208
|
self._mBotMeta = "0.106cm"
|
209
|
+
self._mBotSep = "0.247cm"
|
210
|
+
|
211
|
+
self._mBotFoot = "0.106cm"
|
212
|
+
self._mLeftFoot = "0.600cm"
|
179
213
|
|
180
214
|
# Document Size and Margins
|
181
215
|
self._mDocWidth = "21.0cm"
|
@@ -186,13 +220,15 @@ class ToOdt(Tokenizer):
|
|
186
220
|
self._mDocRight = "2.000cm"
|
187
221
|
|
188
222
|
# Colour
|
189
|
-
self._colHead12
|
190
|
-
self._opaHead12
|
191
|
-
self._colHead34
|
192
|
-
self._opaHead34
|
193
|
-
self._colMetaTx
|
194
|
-
self._opaMetaTx
|
195
|
-
self.
|
223
|
+
self._colHead12 = None
|
224
|
+
self._opaHead12 = None
|
225
|
+
self._colHead34 = None
|
226
|
+
self._opaHead34 = None
|
227
|
+
self._colMetaTx = None
|
228
|
+
self._opaMetaTx = None
|
229
|
+
self._colDialogM = None
|
230
|
+
self._colDialogA = None
|
231
|
+
self._markText = "#ffffa6"
|
196
232
|
|
197
233
|
return
|
198
234
|
|
@@ -232,11 +268,6 @@ class ToOdt(Tokenizer):
|
|
232
268
|
self._pageOffset = offset
|
233
269
|
return
|
234
270
|
|
235
|
-
def setFirstLineIndent(self, state: bool) -> None:
|
236
|
-
"""Enable or disable first line indent."""
|
237
|
-
self._firstIndent = state
|
238
|
-
return
|
239
|
-
|
240
271
|
##
|
241
272
|
# Class Methods
|
242
273
|
##
|
@@ -246,18 +277,25 @@ class ToOdt(Tokenizer):
|
|
246
277
|
# Initialise Variables
|
247
278
|
# ====================
|
248
279
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
self.
|
255
|
-
self.
|
256
|
-
self.
|
257
|
-
self.
|
258
|
-
self.
|
259
|
-
|
260
|
-
self.
|
280
|
+
intWeight = FONT_WEIGHTS.get(self._textFont.weight(), 400)
|
281
|
+
fontWeight = str(intWeight)
|
282
|
+
fontBold = str(min(intWeight + 300, 900))
|
283
|
+
|
284
|
+
self._fontFamily = self._textFont.family()
|
285
|
+
self._fontSize = self._textFont.pointSize()
|
286
|
+
self._fontWeight = FONT_WEIGHT_MAP.get(fontWeight, fontWeight)
|
287
|
+
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
|
288
|
+
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable"
|
289
|
+
self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold)
|
290
|
+
|
291
|
+
self._fSizeTitle = f"{round(2.50 * self._fontSize):d}pt"
|
292
|
+
self._fSizeHead1 = f"{round(2.00 * self._fontSize):d}pt"
|
293
|
+
self._fSizeHead2 = f"{round(1.60 * self._fontSize):d}pt"
|
294
|
+
self._fSizeHead3 = f"{round(1.30 * self._fontSize):d}pt"
|
295
|
+
self._fSizeHead4 = f"{round(1.15 * self._fontSize):d}pt"
|
296
|
+
self._fSizeHead = f"{round(1.15 * self._fontSize):d}pt"
|
297
|
+
self._fSizeText = f"{self._fontSize:d}pt"
|
298
|
+
self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt"
|
261
299
|
|
262
300
|
mScale = self._lineHeight/1.15
|
263
301
|
|
@@ -269,6 +307,7 @@ class ToOdt(Tokenizer):
|
|
269
307
|
self._mTopHead = self._emToCm(mScale * self._marginHead4[0])
|
270
308
|
self._mTopText = self._emToCm(mScale * self._marginText[0])
|
271
309
|
self._mTopMeta = self._emToCm(mScale * self._marginMeta[0])
|
310
|
+
self._mTopSep = self._emToCm(mScale * self._marginSep[0])
|
272
311
|
|
273
312
|
self._mBotTitle = self._emToCm(mScale * self._marginTitle[1])
|
274
313
|
self._mBotHead1 = self._emToCm(mScale * self._marginHead1[1])
|
@@ -278,6 +317,10 @@ class ToOdt(Tokenizer):
|
|
278
317
|
self._mBotHead = self._emToCm(mScale * self._marginHead4[1])
|
279
318
|
self._mBotText = self._emToCm(mScale * self._marginText[1])
|
280
319
|
self._mBotMeta = self._emToCm(mScale * self._marginMeta[1])
|
320
|
+
self._mBotSep = self._emToCm(mScale * self._marginSep[1])
|
321
|
+
|
322
|
+
self._mLeftFoot = self._emToCm(self._marginFoot[0])
|
323
|
+
self._mBotFoot = self._emToCm(self._marginFoot[1])
|
281
324
|
|
282
325
|
if self._colourHead:
|
283
326
|
self._colHead12 = "#2a6099"
|
@@ -287,8 +330,13 @@ class ToOdt(Tokenizer):
|
|
287
330
|
self._colMetaTx = "#813709"
|
288
331
|
self._opaMetaTx = "100%"
|
289
332
|
|
333
|
+
if self._showDialog:
|
334
|
+
self._colDialogM = "#2a6099"
|
335
|
+
self._colDialogA = "#813709"
|
336
|
+
|
290
337
|
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
|
291
338
|
self._fBlockIndent = self._emToCm(self._blockIndent)
|
339
|
+
self._fTextIndent = self._emToCm(self._firstWidth)
|
292
340
|
self._textAlign = "justify" if self._doJustify else "left"
|
293
341
|
|
294
342
|
# Clear Errors
|
@@ -301,7 +349,7 @@ class ToOdt(Tokenizer):
|
|
301
349
|
tAttr[_mkTag("office", "version")] = X_VERS
|
302
350
|
|
303
351
|
fAttr = {}
|
304
|
-
fAttr[_mkTag("style", "name")] = self.
|
352
|
+
fAttr[_mkTag("style", "name")] = self._fontFamily
|
305
353
|
fAttr[_mkTag("style", "font-pitch")] = self._fontPitch
|
306
354
|
|
307
355
|
if self._isFlat:
|
@@ -399,10 +447,7 @@ class ToOdt(Tokenizer):
|
|
399
447
|
"""Convert the list of text tokens into XML elements."""
|
400
448
|
self._result = "" # Not used, but cleared just in case
|
401
449
|
|
402
|
-
|
403
|
-
pText = []
|
404
|
-
pStyle = None
|
405
|
-
pIndent = True
|
450
|
+
xText = self._xText
|
406
451
|
for tType, _, tText, tFormat, tStyle in self._tokens:
|
407
452
|
|
408
453
|
# Styles
|
@@ -419,7 +464,6 @@ class ToOdt(Tokenizer):
|
|
419
464
|
|
420
465
|
if tStyle & self.A_PBB:
|
421
466
|
oStyle.setBreakBefore("page")
|
422
|
-
|
423
467
|
if tStyle & self.A_PBA:
|
424
468
|
oStyle.setBreakAfter("page")
|
425
469
|
|
@@ -433,82 +477,57 @@ class ToOdt(Tokenizer):
|
|
433
477
|
if tStyle & self.A_IND_R:
|
434
478
|
oStyle.setMarginRight(self._fBlockIndent)
|
435
479
|
|
436
|
-
if tType not in (self.T_EMPTY, self.T_TEXT):
|
437
|
-
pIndent = False
|
438
|
-
|
439
480
|
# Process Text Types
|
440
|
-
if tType == self.
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
tFmt = []
|
448
|
-
for nText, nFmt in zip(pText, pFmt):
|
449
|
-
tLen = len(tTxt)
|
450
|
-
tTxt += f"{nText}\n"
|
451
|
-
tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
|
452
|
-
|
453
|
-
# Don't indent a paragraph if it has alignment set
|
454
|
-
tIndent = self._firstIndent and pIndent and pStyle.isUnaligned()
|
455
|
-
self._addTextPar(
|
456
|
-
"First_20_line_20_indent" if tIndent else "Text_20_body",
|
457
|
-
pStyle, tTxt.rstrip(), tFmt=tFmt
|
458
|
-
)
|
459
|
-
pIndent = True
|
460
|
-
|
461
|
-
pFmt = []
|
462
|
-
pText = []
|
463
|
-
pStyle = None
|
481
|
+
if tType == self.T_TEXT:
|
482
|
+
# Text indentation is processed here because there is a
|
483
|
+
# dedicated pre-defined style for it
|
484
|
+
if tStyle & self.A_IND_T:
|
485
|
+
self._addTextPar(xText, S_FIND, oStyle, tText, tFmt=tFormat)
|
486
|
+
else:
|
487
|
+
self._addTextPar(xText, S_TEXT, oStyle, tText, tFmt=tFormat)
|
464
488
|
|
465
489
|
elif tType == self.T_TITLE:
|
490
|
+
# Title must be text:p
|
466
491
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
467
|
-
self._addTextPar(
|
492
|
+
self._addTextPar(xText, S_TITLE, oStyle, tHead, isHead=False)
|
468
493
|
|
469
494
|
elif tType == self.T_HEAD1:
|
470
495
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
471
|
-
self._addTextPar(
|
496
|
+
self._addTextPar(xText, S_HEAD1, oStyle, tHead, isHead=True, oLevel="1")
|
472
497
|
|
473
498
|
elif tType == self.T_HEAD2:
|
474
499
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
475
|
-
self._addTextPar(
|
500
|
+
self._addTextPar(xText, S_HEAD2, oStyle, tHead, isHead=True, oLevel="2")
|
476
501
|
|
477
502
|
elif tType == self.T_HEAD3:
|
478
503
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
479
|
-
self._addTextPar(
|
504
|
+
self._addTextPar(xText, S_HEAD3, oStyle, tHead, isHead=True, oLevel="3")
|
480
505
|
|
481
506
|
elif tType == self.T_HEAD4:
|
482
507
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
483
|
-
self._addTextPar(
|
508
|
+
self._addTextPar(xText, S_HEAD4, oStyle, tHead, isHead=True, oLevel="4")
|
484
509
|
|
485
510
|
elif tType == self.T_SEP:
|
486
|
-
self._addTextPar(
|
511
|
+
self._addTextPar(xText, S_SEP, oStyle, tText)
|
487
512
|
|
488
513
|
elif tType == self.T_SKIP:
|
489
|
-
self._addTextPar(
|
490
|
-
|
491
|
-
elif tType == self.T_TEXT:
|
492
|
-
if pStyle is None:
|
493
|
-
pStyle = oStyle
|
494
|
-
pText.append(tText)
|
495
|
-
pFmt.append(tFormat)
|
514
|
+
self._addTextPar(xText, S_TEXT, oStyle, "")
|
496
515
|
|
497
516
|
elif tType == self.T_SYNOPSIS and self._doSynopsis:
|
498
|
-
tTemp,
|
499
|
-
self._addTextPar(
|
517
|
+
tTemp, tFmt = self._formatSynopsis(tText, tFormat, True)
|
518
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
500
519
|
|
501
520
|
elif tType == self.T_SHORT and self._doSynopsis:
|
502
|
-
tTemp,
|
503
|
-
self._addTextPar(
|
521
|
+
tTemp, tFmt = self._formatSynopsis(tText, tFormat, False)
|
522
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
504
523
|
|
505
524
|
elif tType == self.T_COMMENT and self._doComments:
|
506
|
-
tTemp,
|
507
|
-
self._addTextPar(
|
525
|
+
tTemp, tFmt = self._formatComments(tText, tFormat)
|
526
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
508
527
|
|
509
528
|
elif tType == self.T_KEYWORD and self._doKeywords:
|
510
|
-
tTemp,
|
511
|
-
self._addTextPar(
|
529
|
+
tTemp, tFmt = self._formatKeywords(tText)
|
530
|
+
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
512
531
|
|
513
532
|
return
|
514
533
|
|
@@ -569,28 +588,32 @@ class ToOdt(Tokenizer):
|
|
569
588
|
# Internal Functions
|
570
589
|
##
|
571
590
|
|
572
|
-
def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str,
|
591
|
+
def _formatSynopsis(self, text: str, fmt: T_Formats, synopsis: bool) -> tuple[str, T_Formats]:
|
573
592
|
"""Apply formatting to synopsis lines."""
|
574
593
|
name = self._localLookup("Synopsis" if synopsis else "Short Description")
|
594
|
+
shift = len(name) + 2
|
575
595
|
rTxt = f"{name}: {text}"
|
576
|
-
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
|
596
|
+
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
|
597
|
+
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
|
577
598
|
return rTxt, rFmt
|
578
599
|
|
579
|
-
def _formatComments(self, text: str) -> tuple[str,
|
600
|
+
def _formatComments(self, text: str, fmt: T_Formats) -> tuple[str, T_Formats]:
|
580
601
|
"""Apply formatting to comments."""
|
581
602
|
name = self._localLookup("Comment")
|
603
|
+
shift = len(name) + 2
|
582
604
|
rTxt = f"{name}: {text}"
|
583
|
-
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
|
605
|
+
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")]
|
606
|
+
rFmt.extend((p + shift, f, d) for p, f, d in fmt)
|
584
607
|
return rTxt, rFmt
|
585
608
|
|
586
|
-
def _formatKeywords(self, text: str) -> tuple[str,
|
609
|
+
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
|
587
610
|
"""Apply formatting to keywords."""
|
588
611
|
valid, bits, _ = self._project.index.scanThis("@"+text)
|
589
612
|
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
590
613
|
return "", []
|
591
614
|
|
592
615
|
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
593
|
-
rFmt = [(0, self.FMT_B_B), (len(rTxt) - 1, self.FMT_B_E)]
|
616
|
+
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(rTxt) - 1, self.FMT_B_E, "")]
|
594
617
|
if len(bits) > 1:
|
595
618
|
if bits[0] == nwKeyWords.TAG_KEY:
|
596
619
|
rTxt += bits[1]
|
@@ -600,8 +623,8 @@ class ToOdt(Tokenizer):
|
|
600
623
|
return rTxt, rFmt
|
601
624
|
|
602
625
|
def _addTextPar(
|
603
|
-
self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
|
604
|
-
tFmt: Sequence[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
|
626
|
+
self, xParent: ET.Element, styleName: str, oStyle: ODTParagraphStyle, tText: str,
|
627
|
+
tFmt: Sequence[tuple[int, int, str]] = [], isHead: bool = False, oLevel: str | None = None
|
605
628
|
) -> None:
|
606
629
|
"""Add a text paragraph to the text XML element."""
|
607
630
|
tAttr = {_mkTag("text", "style-name"): self._paraStyle(styleName, oStyle)}
|
@@ -609,7 +632,7 @@ class ToOdt(Tokenizer):
|
|
609
632
|
tAttr[_mkTag("text", "outline-level")] = oLevel
|
610
633
|
|
611
634
|
pTag = "h" if isHead else "p"
|
612
|
-
xElem = ET.SubElement(
|
635
|
+
xElem = ET.SubElement(xParent, _mkTag("text", pTag), attrib=tAttr)
|
613
636
|
|
614
637
|
# It's important to set the initial text field to empty, otherwise
|
615
638
|
# xmlIndent will add a line break if the first subelement is a span.
|
@@ -627,7 +650,13 @@ class ToOdt(Tokenizer):
|
|
627
650
|
xFmt = 0x00
|
628
651
|
tFrag = ""
|
629
652
|
fLast = 0
|
630
|
-
|
653
|
+
xNode = None
|
654
|
+
for fPos, fFmt, fData in tFmt:
|
655
|
+
|
656
|
+
# Add any extra nodes
|
657
|
+
if xNode is not None:
|
658
|
+
parProc.appendNode(xNode)
|
659
|
+
xNode = None
|
631
660
|
|
632
661
|
# Add the text up to the current fragment
|
633
662
|
if tFrag := tText[fLast:fPos]:
|
@@ -665,11 +694,26 @@ class ToOdt(Tokenizer):
|
|
665
694
|
xFmt |= X_SUB
|
666
695
|
elif fFmt == self.FMT_SUB_E:
|
667
696
|
xFmt &= M_SUB
|
697
|
+
elif fFmt == self.FMT_DL_B:
|
698
|
+
xFmt |= X_DLG
|
699
|
+
elif fFmt == self.FMT_DL_E:
|
700
|
+
xFmt &= M_DLG
|
701
|
+
elif fFmt == self.FMT_ADL_B:
|
702
|
+
xFmt |= X_DLA
|
703
|
+
elif fFmt == self.FMT_ADL_E:
|
704
|
+
xFmt &= M_DLA
|
705
|
+
elif fFmt == self.FMT_FNOTE:
|
706
|
+
xNode = self._generateFootnote(fData)
|
707
|
+
elif fFmt == self.FMT_STRIP:
|
708
|
+
pass
|
668
709
|
else:
|
669
710
|
pErr += 1
|
670
711
|
|
671
712
|
fLast = fPos
|
672
713
|
|
714
|
+
if xNode is not None:
|
715
|
+
parProc.appendNode(xNode)
|
716
|
+
|
673
717
|
if tFrag := tText[fLast:]:
|
674
718
|
if xFmt == 0x00:
|
675
719
|
parProc.appendText(tFrag)
|
@@ -715,7 +759,7 @@ class ToOdt(Tokenizer):
|
|
715
759
|
|
716
760
|
style = ODTTextStyle(f"T{len(self._autoText)+1:d}")
|
717
761
|
if hFmt & X_BLD:
|
718
|
-
style.setFontWeight(
|
762
|
+
style.setFontWeight(self._fontBold)
|
719
763
|
if hFmt & X_ITA:
|
720
764
|
style.setFontStyle("italic")
|
721
765
|
if hFmt & X_DEL:
|
@@ -731,13 +775,33 @@ class ToOdt(Tokenizer):
|
|
731
775
|
style.setTextPosition("super")
|
732
776
|
if hFmt & X_SUB:
|
733
777
|
style.setTextPosition("sub")
|
778
|
+
if hFmt & X_DLG:
|
779
|
+
style.setColour(self._colDialogM)
|
780
|
+
if hFmt & X_DLA:
|
781
|
+
style.setColour(self._colDialogA)
|
734
782
|
self._autoText[hFmt] = style
|
735
783
|
|
736
784
|
return style.name
|
737
785
|
|
786
|
+
def _generateFootnote(self, key: str) -> ET.Element | None:
|
787
|
+
"""Generate a footnote XML object."""
|
788
|
+
if content := self._footnotes.get(key):
|
789
|
+
self._nNote += 1
|
790
|
+
nStyle = ODTParagraphStyle("New")
|
791
|
+
xNote = ET.Element(_mkTag("text", "note"), attrib={
|
792
|
+
_mkTag("text", "id"): f"ftn{self._nNote}",
|
793
|
+
_mkTag("text", "note-class"): "footnote",
|
794
|
+
})
|
795
|
+
xCite = ET.SubElement(xNote, _mkTag("text", "note-citation"))
|
796
|
+
xCite.text = str(self._nNote)
|
797
|
+
xBody = ET.SubElement(xNote, _mkTag("text", "note-body"))
|
798
|
+
self._addTextPar(xBody, "Footnote", nStyle, content[0], tFmt=content[1])
|
799
|
+
return xNote
|
800
|
+
return None
|
801
|
+
|
738
802
|
def _emToCm(self, value: float) -> str:
|
739
803
|
"""Converts an em value to centimetres."""
|
740
|
-
return f"{value*2.54/72*self.
|
804
|
+
return f"{value*2.54/72*self._fontSize:.3f}cm"
|
741
805
|
|
742
806
|
##
|
743
807
|
# Style Elements
|
@@ -757,7 +821,6 @@ class ToOdt(Tokenizer):
|
|
757
821
|
_mkTag("fo", "margin-bottom"): self._mDocBtm,
|
758
822
|
_mkTag("fo", "margin-left"): self._mDocLeft,
|
759
823
|
_mkTag("fo", "margin-right"): self._mDocRight,
|
760
|
-
_mkTag("fo", "print-orientation"): "portrait",
|
761
824
|
})
|
762
825
|
|
763
826
|
xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
|
@@ -782,8 +845,10 @@ class ToOdt(Tokenizer):
|
|
782
845
|
_mkTag("style", "writing-mode"): "page",
|
783
846
|
})
|
784
847
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
785
|
-
_mkTag("style", "font-name"): self.
|
848
|
+
_mkTag("style", "font-name"): self._fontFamily,
|
786
849
|
_mkTag("fo", "font-family"): self._fontFamily,
|
850
|
+
_mkTag("fo", "font-weight"): self._fontWeight,
|
851
|
+
_mkTag("fo", "font-style"): self._fontStyle,
|
787
852
|
_mkTag("fo", "font-size"): self._fSizeText,
|
788
853
|
_mkTag("fo", "language"): self._dLanguage,
|
789
854
|
_mkTag("fo", "country"): self._dCountry,
|
@@ -796,8 +861,10 @@ class ToOdt(Tokenizer):
|
|
796
861
|
_mkTag("style", "class"): "text",
|
797
862
|
})
|
798
863
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
799
|
-
_mkTag("style", "font-name"): self.
|
864
|
+
_mkTag("style", "font-name"): self._fontFamily,
|
800
865
|
_mkTag("fo", "font-family"): self._fontFamily,
|
866
|
+
_mkTag("fo", "font-weight"): self._fontWeight,
|
867
|
+
_mkTag("fo", "font-style"): self._fontStyle,
|
801
868
|
_mkTag("fo", "font-size"): self._fSizeText,
|
802
869
|
})
|
803
870
|
|
@@ -806,7 +873,7 @@ class ToOdt(Tokenizer):
|
|
806
873
|
_mkTag("style", "name"): "Heading",
|
807
874
|
_mkTag("style", "family"): "paragraph",
|
808
875
|
_mkTag("style", "parent-style-name"): "Standard",
|
809
|
-
_mkTag("style", "next-style-name"):
|
876
|
+
_mkTag("style", "next-style-name"): S_TEXT,
|
810
877
|
_mkTag("style", "class"): "text",
|
811
878
|
})
|
812
879
|
ET.SubElement(xStyl, _mkTag("style", "paragraph-properties"), attrib={
|
@@ -815,14 +882,16 @@ class ToOdt(Tokenizer):
|
|
815
882
|
_mkTag("fo", "keep-with-next"): "always",
|
816
883
|
})
|
817
884
|
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
|
818
|
-
_mkTag("style", "font-name"): self.
|
885
|
+
_mkTag("style", "font-name"): self._fontFamily,
|
819
886
|
_mkTag("fo", "font-family"): self._fontFamily,
|
887
|
+
_mkTag("fo", "font-weight"): self._fontWeight,
|
888
|
+
_mkTag("fo", "font-style"): self._fontStyle,
|
820
889
|
_mkTag("fo", "font-size"): self._fSizeHead,
|
821
890
|
})
|
822
891
|
|
823
892
|
# Add Header and Footer Styles
|
824
893
|
ET.SubElement(self._xStyl, _mkTag("style", "style"), attrib={
|
825
|
-
_mkTag("style", "name"):
|
894
|
+
_mkTag("style", "name"): S_HNF,
|
826
895
|
_mkTag("style", "display-name"): "Header and Footer",
|
827
896
|
_mkTag("style", "family"): "paragraph",
|
828
897
|
_mkTag("style", "parent-style-name"): "Standard",
|
@@ -834,7 +903,7 @@ class ToOdt(Tokenizer):
|
|
834
903
|
def _useableStyles(self) -> None:
|
835
904
|
"""Set the usable styles."""
|
836
905
|
# Add Text Body Style
|
837
|
-
style = ODTParagraphStyle(
|
906
|
+
style = ODTParagraphStyle(S_TEXT)
|
838
907
|
style.setDisplayName("Text body")
|
839
908
|
style.setParentStyleName("Standard")
|
840
909
|
style.setClass("text")
|
@@ -842,136 +911,139 @@ class ToOdt(Tokenizer):
|
|
842
911
|
style.setMarginBottom(self._mBotText)
|
843
912
|
style.setLineHeight(self._fLineHeight)
|
844
913
|
style.setTextAlign(self._textAlign)
|
845
|
-
style.setFontName(self.
|
914
|
+
style.setFontName(self._fontFamily)
|
846
915
|
style.setFontFamily(self._fontFamily)
|
847
916
|
style.setFontSize(self._fSizeText)
|
917
|
+
style.setFontWeight(self._fontWeight)
|
848
918
|
style.packXML(self._xStyl)
|
849
919
|
self._mainPara[style.name] = style
|
850
920
|
|
851
921
|
# Add First Line Indent Style
|
852
|
-
style = ODTParagraphStyle(
|
922
|
+
style = ODTParagraphStyle(S_FIND)
|
853
923
|
style.setDisplayName("First line indent")
|
854
|
-
style.setParentStyleName(
|
924
|
+
style.setParentStyleName(S_TEXT)
|
855
925
|
style.setClass("text")
|
856
926
|
style.setTextIndent(self._fTextIndent)
|
857
927
|
style.packXML(self._xStyl)
|
858
928
|
self._mainPara[style.name] = style
|
859
929
|
|
860
930
|
# Add Text Meta Style
|
861
|
-
style = ODTParagraphStyle(
|
931
|
+
style = ODTParagraphStyle(S_META)
|
862
932
|
style.setDisplayName("Text Meta")
|
863
933
|
style.setParentStyleName("Standard")
|
864
934
|
style.setClass("text")
|
865
935
|
style.setMarginTop(self._mTopMeta)
|
866
936
|
style.setMarginBottom(self._mBotMeta)
|
867
937
|
style.setLineHeight(self._fLineHeight)
|
868
|
-
style.setFontName(self.
|
938
|
+
style.setFontName(self._fontFamily)
|
869
939
|
style.setFontFamily(self._fontFamily)
|
870
940
|
style.setFontSize(self._fSizeText)
|
941
|
+
style.setFontWeight(self._fontWeight)
|
871
942
|
style.setColour(self._colMetaTx)
|
872
943
|
style.setOpacity(self._opaMetaTx)
|
873
944
|
style.packXML(self._xStyl)
|
874
945
|
self._mainPara[style.name] = style
|
875
946
|
|
876
947
|
# Add Title Style
|
877
|
-
style = ODTParagraphStyle(
|
948
|
+
style = ODTParagraphStyle(S_TITLE)
|
878
949
|
style.setDisplayName("Title")
|
879
950
|
style.setParentStyleName("Heading")
|
880
|
-
style.setNextStyleName(
|
951
|
+
style.setNextStyleName(S_TEXT)
|
881
952
|
style.setClass("chapter")
|
882
953
|
style.setMarginTop(self._mTopTitle)
|
883
954
|
style.setMarginBottom(self._mBotTitle)
|
884
955
|
style.setTextAlign("center")
|
885
|
-
style.setFontName(self.
|
956
|
+
style.setFontName(self._fontFamily)
|
886
957
|
style.setFontFamily(self._fontFamily)
|
887
958
|
style.setFontSize(self._fSizeTitle)
|
888
|
-
style.setFontWeight(
|
959
|
+
style.setFontWeight(self._fontBold)
|
889
960
|
style.packXML(self._xStyl)
|
890
961
|
self._mainPara[style.name] = style
|
891
962
|
|
892
963
|
# Add Separator Style
|
893
|
-
style = ODTParagraphStyle(
|
964
|
+
style = ODTParagraphStyle(S_SEP)
|
894
965
|
style.setDisplayName("Separator")
|
895
966
|
style.setParentStyleName("Standard")
|
896
|
-
style.setNextStyleName(
|
967
|
+
style.setNextStyleName(S_TEXT)
|
897
968
|
style.setClass("text")
|
898
|
-
style.setMarginTop(self.
|
899
|
-
style.setMarginBottom(self.
|
969
|
+
style.setMarginTop(self._mTopSep)
|
970
|
+
style.setMarginBottom(self._mBotSep)
|
900
971
|
style.setLineHeight(self._fLineHeight)
|
901
972
|
style.setTextAlign("center")
|
902
|
-
style.setFontName(self.
|
973
|
+
style.setFontName(self._fontFamily)
|
903
974
|
style.setFontFamily(self._fontFamily)
|
904
975
|
style.setFontSize(self._fSizeText)
|
976
|
+
style.setFontWeight(self._fontWeight)
|
905
977
|
style.packXML(self._xStyl)
|
906
978
|
self._mainPara[style.name] = style
|
907
979
|
|
908
980
|
# Add Heading 1 Style
|
909
|
-
style = ODTParagraphStyle(
|
981
|
+
style = ODTParagraphStyle(S_HEAD1)
|
910
982
|
style.setDisplayName("Heading 1")
|
911
983
|
style.setParentStyleName("Heading")
|
912
|
-
style.setNextStyleName(
|
984
|
+
style.setNextStyleName(S_TEXT)
|
913
985
|
style.setOutlineLevel("1")
|
914
986
|
style.setClass("text")
|
915
987
|
style.setMarginTop(self._mTopHead1)
|
916
988
|
style.setMarginBottom(self._mBotHead1)
|
917
|
-
style.setFontName(self.
|
989
|
+
style.setFontName(self._fontFamily)
|
918
990
|
style.setFontFamily(self._fontFamily)
|
919
991
|
style.setFontSize(self._fSizeHead1)
|
920
|
-
style.setFontWeight(
|
992
|
+
style.setFontWeight(self._fontBold)
|
921
993
|
style.setColour(self._colHead12)
|
922
994
|
style.setOpacity(self._opaHead12)
|
923
995
|
style.packXML(self._xStyl)
|
924
996
|
self._mainPara[style.name] = style
|
925
997
|
|
926
998
|
# Add Heading 2 Style
|
927
|
-
style = ODTParagraphStyle(
|
999
|
+
style = ODTParagraphStyle(S_HEAD2)
|
928
1000
|
style.setDisplayName("Heading 2")
|
929
1001
|
style.setParentStyleName("Heading")
|
930
|
-
style.setNextStyleName(
|
1002
|
+
style.setNextStyleName(S_TEXT)
|
931
1003
|
style.setOutlineLevel("2")
|
932
1004
|
style.setClass("text")
|
933
1005
|
style.setMarginTop(self._mTopHead2)
|
934
1006
|
style.setMarginBottom(self._mBotHead2)
|
935
|
-
style.setFontName(self.
|
1007
|
+
style.setFontName(self._fontFamily)
|
936
1008
|
style.setFontFamily(self._fontFamily)
|
937
1009
|
style.setFontSize(self._fSizeHead2)
|
938
|
-
style.setFontWeight(
|
1010
|
+
style.setFontWeight(self._fontBold)
|
939
1011
|
style.setColour(self._colHead12)
|
940
1012
|
style.setOpacity(self._opaHead12)
|
941
1013
|
style.packXML(self._xStyl)
|
942
1014
|
self._mainPara[style.name] = style
|
943
1015
|
|
944
1016
|
# Add Heading 3 Style
|
945
|
-
style = ODTParagraphStyle(
|
1017
|
+
style = ODTParagraphStyle(S_HEAD3)
|
946
1018
|
style.setDisplayName("Heading 3")
|
947
1019
|
style.setParentStyleName("Heading")
|
948
|
-
style.setNextStyleName(
|
1020
|
+
style.setNextStyleName(S_TEXT)
|
949
1021
|
style.setOutlineLevel("3")
|
950
1022
|
style.setClass("text")
|
951
1023
|
style.setMarginTop(self._mTopHead3)
|
952
1024
|
style.setMarginBottom(self._mBotHead3)
|
953
|
-
style.setFontName(self.
|
1025
|
+
style.setFontName(self._fontFamily)
|
954
1026
|
style.setFontFamily(self._fontFamily)
|
955
1027
|
style.setFontSize(self._fSizeHead3)
|
956
|
-
style.setFontWeight(
|
1028
|
+
style.setFontWeight(self._fontBold)
|
957
1029
|
style.setColour(self._colHead34)
|
958
1030
|
style.setOpacity(self._opaHead34)
|
959
1031
|
style.packXML(self._xStyl)
|
960
1032
|
self._mainPara[style.name] = style
|
961
1033
|
|
962
1034
|
# Add Heading 4 Style
|
963
|
-
style = ODTParagraphStyle(
|
1035
|
+
style = ODTParagraphStyle(S_HEAD4)
|
964
1036
|
style.setDisplayName("Heading 4")
|
965
1037
|
style.setParentStyleName("Heading")
|
966
|
-
style.setNextStyleName(
|
1038
|
+
style.setNextStyleName(S_TEXT)
|
967
1039
|
style.setOutlineLevel("4")
|
968
1040
|
style.setClass("text")
|
969
1041
|
style.setMarginTop(self._mTopHead4)
|
970
1042
|
style.setMarginBottom(self._mBotHead4)
|
971
|
-
style.setFontName(self.
|
1043
|
+
style.setFontName(self._fontFamily)
|
972
1044
|
style.setFontFamily(self._fontFamily)
|
973
1045
|
style.setFontSize(self._fSizeHead4)
|
974
|
-
style.setFontWeight(
|
1046
|
+
style.setFontWeight(self._fontBold)
|
975
1047
|
style.setColour(self._colHead34)
|
976
1048
|
style.setOpacity(self._opaHead34)
|
977
1049
|
style.packXML(self._xStyl)
|
@@ -980,11 +1052,23 @@ class ToOdt(Tokenizer):
|
|
980
1052
|
# Add Header Style
|
981
1053
|
style = ODTParagraphStyle("Header")
|
982
1054
|
style.setDisplayName("Header")
|
983
|
-
style.setParentStyleName(
|
1055
|
+
style.setParentStyleName(S_HNF)
|
984
1056
|
style.setTextAlign("right")
|
985
1057
|
style.packXML(self._xStyl)
|
986
1058
|
self._mainPara[style.name] = style
|
987
1059
|
|
1060
|
+
# Add Footnote Style
|
1061
|
+
style = ODTParagraphStyle("Footnote")
|
1062
|
+
style.setDisplayName("Footnote")
|
1063
|
+
style.setParentStyleName("Standard")
|
1064
|
+
style.setClass("extra")
|
1065
|
+
style.setMarginLeft(self._mLeftFoot)
|
1066
|
+
style.setMarginBottom(self._mBotFoot)
|
1067
|
+
style.setTextIndent("-"+self._mLeftFoot)
|
1068
|
+
style.setFontSize(self._fSizeFoot)
|
1069
|
+
style.packXML(self._xStyl)
|
1070
|
+
self._mainPara[style.name] = style
|
1071
|
+
|
988
1072
|
return
|
989
1073
|
|
990
1074
|
def _writeHeader(self) -> None:
|
@@ -1026,12 +1110,9 @@ class ToOdt(Tokenizer):
|
|
1026
1110
|
|
1027
1111
|
return
|
1028
1112
|
|
1029
|
-
# END Class ToOdt
|
1030
|
-
|
1031
1113
|
|
1032
|
-
#
|
1033
|
-
#
|
1034
|
-
# =============================================================================================== #
|
1114
|
+
# Auto-Style Classes
|
1115
|
+
# ==================
|
1035
1116
|
|
1036
1117
|
class ODTParagraphStyle:
|
1037
1118
|
"""Wrapper class for the paragraph style setting used by the
|
@@ -1041,8 +1122,8 @@ class ODTParagraphStyle:
|
|
1041
1122
|
VALID_ALIGN = ["start", "center", "end", "justify", "inside", "outside", "left", "right"]
|
1042
1123
|
VALID_BREAK = ["auto", "column", "page", "even-page", "odd-page", "inherit"]
|
1043
1124
|
VALID_LEVEL = ["1", "2", "3", "4"]
|
1044
|
-
VALID_CLASS = ["text", "chapter"]
|
1045
|
-
VALID_WEIGHT = ["normal", "
|
1125
|
+
VALID_CLASS = ["text", "chapter", "extra"]
|
1126
|
+
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
|
1046
1127
|
|
1047
1128
|
def __init__(self, name: str) -> None:
|
1048
1129
|
|
@@ -1279,16 +1360,14 @@ class ODTParagraphStyle:
|
|
1279
1360
|
|
1280
1361
|
return
|
1281
1362
|
|
1282
|
-
# END Class ODTParagraphStyle
|
1283
|
-
|
1284
1363
|
|
1285
1364
|
class ODTTextStyle:
|
1286
1365
|
"""Wrapper class for the text style setting used by the exporter.
|
1287
1366
|
Only the used settings are exposed here to keep the class minimal
|
1288
1367
|
and fast.
|
1289
1368
|
"""
|
1290
|
-
VALID_WEIGHT = ["normal", "
|
1291
|
-
VALID_STYLE = ["normal", "
|
1369
|
+
VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
|
1370
|
+
VALID_STYLE = ["normal", "italic", "oblique"]
|
1292
1371
|
VALID_POS = ["super", "sub"]
|
1293
1372
|
VALID_LSTYLE = ["none", "solid"]
|
1294
1373
|
VALID_LTYPE = ["single", "double"]
|
@@ -1300,6 +1379,7 @@ class ODTTextStyle:
|
|
1300
1379
|
self._tAttr = {
|
1301
1380
|
"font-weight": ["fo", None],
|
1302
1381
|
"font-style": ["fo", None],
|
1382
|
+
"color": ["fo", None],
|
1303
1383
|
"background-color": ["fo", None],
|
1304
1384
|
"text-position": ["style", None],
|
1305
1385
|
"text-line-through-style": ["style", None],
|
@@ -1334,6 +1414,14 @@ class ODTTextStyle:
|
|
1334
1414
|
self._tAttr["font-style"][1] = None
|
1335
1415
|
return
|
1336
1416
|
|
1417
|
+
def setColour(self, value: str | None) -> None:
|
1418
|
+
"""Set text colour."""
|
1419
|
+
if value and len(value) == 7 and value[0] == "#":
|
1420
|
+
self._tAttr["color"][1] = value
|
1421
|
+
else:
|
1422
|
+
self._tAttr["color"][1] = None
|
1423
|
+
return
|
1424
|
+
|
1337
1425
|
def setBackgroundColour(self, value: str | None) -> None:
|
1338
1426
|
"""Set text background colour."""
|
1339
1427
|
if value and len(value) == 7 and value[0] == "#":
|
@@ -1404,12 +1492,9 @@ class ODTTextStyle:
|
|
1404
1492
|
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
|
1405
1493
|
return
|
1406
1494
|
|
1407
|
-
# END Class ODTTextStyle
|
1408
|
-
|
1409
1495
|
|
1410
|
-
#
|
1411
|
-
#
|
1412
|
-
# =============================================================================================== #
|
1496
|
+
# XML Complex Element Helper Class
|
1497
|
+
# ================================
|
1413
1498
|
|
1414
1499
|
X_ROOT_TEXT = 0
|
1415
1500
|
X_ROOT_TAIL = 1
|
@@ -1464,7 +1549,6 @@ class XMLParagraph:
|
|
1464
1549
|
if c == " ":
|
1465
1550
|
nSpaces += 1
|
1466
1551
|
continue
|
1467
|
-
|
1468
1552
|
elif nSpaces > 0:
|
1469
1553
|
self._processSpaces(nSpaces)
|
1470
1554
|
nSpaces = 0
|
@@ -1475,26 +1559,22 @@ class XMLParagraph:
|
|
1475
1559
|
self._xTail.tail = ""
|
1476
1560
|
self._nState = X_ROOT_TAIL
|
1477
1561
|
self._chrPos += 1
|
1478
|
-
|
1479
1562
|
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
|
1480
1563
|
self._xSing = ET.SubElement(self._xTail, TAG_BR)
|
1481
1564
|
self._xSing.tail = ""
|
1482
1565
|
self._nState = X_SPAN_SING
|
1483
1566
|
self._chrPos += 1
|
1484
|
-
|
1485
1567
|
elif c == "\t":
|
1486
1568
|
if self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
|
1487
1569
|
self._xTail = ET.SubElement(self._xRoot, TAG_TAB)
|
1488
1570
|
self._xTail.tail = ""
|
1489
1571
|
self._nState = X_ROOT_TAIL
|
1490
1572
|
self._chrPos += 1
|
1491
|
-
|
1492
1573
|
elif self._nState in (X_SPAN_TEXT, X_SPAN_SING):
|
1493
1574
|
self._xSing = ET.SubElement(self._xTail, TAG_TAB)
|
1494
1575
|
self._xSing.tail = ""
|
1495
1576
|
self._chrPos += 1
|
1496
1577
|
self._nState = X_SPAN_SING
|
1497
|
-
|
1498
1578
|
else:
|
1499
1579
|
if self._nState == X_ROOT_TEXT:
|
1500
1580
|
self._xRoot.text = (self._xRoot.text or "") + c
|
@@ -1529,6 +1609,19 @@ class XMLParagraph:
|
|
1529
1609
|
self._nState = X_ROOT_TAIL
|
1530
1610
|
return
|
1531
1611
|
|
1612
|
+
def appendNode(self, xNode: ET.Element | None) -> None:
|
1613
|
+
"""Append an XML node to the paragraph. We only check for the
|
1614
|
+
X_ROOT_TEXT and X_ROOT_TAIL states. X_SPAN_TEXT is not possible
|
1615
|
+
at all, and X_SPAN_SING only happens internally in an appendSpan
|
1616
|
+
call, returning us to an X_ROOT_TAIL state.
|
1617
|
+
"""
|
1618
|
+
if xNode is not None and self._nState in (X_ROOT_TEXT, X_ROOT_TAIL):
|
1619
|
+
self._xRoot.append(xNode)
|
1620
|
+
self._xTail = xNode
|
1621
|
+
self._xTail.tail = ""
|
1622
|
+
self._nState = X_ROOT_TAIL
|
1623
|
+
return
|
1624
|
+
|
1532
1625
|
def checkError(self) -> tuple[int, str]:
|
1533
1626
|
"""Check that the number of characters written matches the
|
1534
1627
|
number of characters received.
|
@@ -1600,5 +1693,3 @@ class XMLParagraph:
|
|
1600
1693
|
self._chrPos += nSpaces - 1
|
1601
1694
|
|
1602
1695
|
return
|
1603
|
-
|
1604
|
-
# END Class XMLParagraph
|