novelWriter 2.5.1__py3-none-any.whl → 2.6b1__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.5.1.dist-info → novelWriter-2.6b1.dist-info}/METADATA +2 -1
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/RECORD +61 -56
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +3 -3
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +1 -0
- novelwriter/assets/icons/typicons_dark/mixed_copy.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +1 -0
- novelwriter/assets/icons/typicons_light/mixed_copy.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +63 -0
- novelwriter/config.py +10 -3
- novelwriter/constants.py +153 -60
- novelwriter/core/buildsettings.py +66 -39
- novelwriter/core/coretools.py +34 -22
- novelwriter/core/docbuild.py +130 -169
- novelwriter/core/index.py +29 -18
- novelwriter/core/item.py +2 -2
- novelwriter/core/options.py +4 -1
- novelwriter/core/spellcheck.py +9 -14
- novelwriter/dialogs/preferences.py +45 -32
- novelwriter/dialogs/projectsettings.py +3 -3
- novelwriter/enum.py +29 -23
- novelwriter/extensions/configlayout.py +24 -11
- novelwriter/extensions/modified.py +13 -1
- novelwriter/extensions/pagedsidebar.py +5 -5
- novelwriter/formats/shared.py +155 -0
- novelwriter/formats/todocx.py +1195 -0
- novelwriter/formats/tohtml.py +452 -0
- novelwriter/{core → formats}/tokenizer.py +483 -485
- novelwriter/formats/tomarkdown.py +217 -0
- novelwriter/{core → formats}/toodt.py +270 -320
- novelwriter/formats/toqdoc.py +436 -0
- novelwriter/formats/toraw.py +91 -0
- novelwriter/gui/doceditor.py +240 -193
- novelwriter/gui/dochighlight.py +96 -84
- novelwriter/gui/docviewer.py +56 -30
- novelwriter/gui/docviewerpanel.py +3 -3
- novelwriter/gui/editordocument.py +17 -2
- novelwriter/gui/itemdetails.py +8 -4
- novelwriter/gui/mainmenu.py +121 -60
- novelwriter/gui/noveltree.py +35 -37
- novelwriter/gui/outline.py +186 -238
- novelwriter/gui/projtree.py +142 -131
- novelwriter/gui/sidebar.py +7 -6
- novelwriter/gui/theme.py +5 -4
- novelwriter/guimain.py +43 -155
- novelwriter/shared.py +14 -4
- novelwriter/text/counting.py +2 -0
- novelwriter/text/patterns.py +155 -59
- novelwriter/tools/manusbuild.py +1 -1
- novelwriter/tools/manuscript.py +121 -78
- novelwriter/tools/manussettings.py +403 -260
- novelwriter/tools/welcome.py +4 -4
- novelwriter/tools/writingstats.py +3 -3
- novelwriter/types.py +16 -6
- novelwriter/core/tohtml.py +0 -530
- novelwriter/core/tomarkdown.py +0 -252
- novelwriter/core/toqdoc.py +0 -419
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.5.1.dist-info → novelWriter-2.6b1.dist-info}/top_level.txt +0 -0
@@ -33,29 +33,32 @@ from collections.abc import Sequence
|
|
33
33
|
from datetime import datetime
|
34
34
|
from hashlib import sha256
|
35
35
|
from pathlib import Path
|
36
|
-
from zipfile import ZipFile
|
36
|
+
from zipfile import ZIP_DEFLATED, ZipFile
|
37
37
|
|
38
|
-
from PyQt5.QtGui import QFont
|
38
|
+
from PyQt5.QtGui import QColor, QFont
|
39
39
|
|
40
40
|
from novelwriter import __version__
|
41
|
-
from novelwriter.common import xmlIndent
|
42
|
-
from novelwriter.constants import nwHeadFmt,
|
41
|
+
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
|
42
|
+
from novelwriter.constants import nwHeadFmt, nwStyles
|
43
43
|
from novelwriter.core.project import NWProject
|
44
|
-
from novelwriter.
|
45
|
-
from novelwriter.
|
44
|
+
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
|
45
|
+
from novelwriter.formats.tokenizer import Tokenizer
|
46
|
+
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
|
46
47
|
|
47
48
|
logger = logging.getLogger(__name__)
|
48
49
|
|
49
50
|
# Main XML NameSpaces
|
50
51
|
XML_NS = {
|
52
|
+
"dc": "http://purl.org/dc/elements/1.1/",
|
53
|
+
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
54
|
+
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
|
51
55
|
"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
|
56
|
+
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
|
57
|
+
"number": "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
|
52
58
|
"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
53
59
|
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
54
|
-
"loext": "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
|
55
60
|
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
56
|
-
"
|
57
|
-
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
58
|
-
"dc": "http://purl.org/dc/elements/1.1/",
|
61
|
+
"xlink": "http://www.w3.org/1999/xlink",
|
59
62
|
}
|
60
63
|
for ns, uri in XML_NS.items():
|
61
64
|
ET.register_namespace(ns, uri)
|
@@ -79,7 +82,6 @@ TAG_SPC = _mkTag("text", "s")
|
|
79
82
|
TAG_NSPC = _mkTag("text", "c")
|
80
83
|
TAG_TAB = _mkTag("text", "tab")
|
81
84
|
TAG_SPAN = _mkTag("text", "span")
|
82
|
-
TAG_STNM = _mkTag("text", "style-name")
|
83
85
|
|
84
86
|
# Formatting Codes
|
85
87
|
X_BLD = 0x001 # Bold format
|
@@ -89,8 +91,8 @@ X_UND = 0x008 # Underline format
|
|
89
91
|
X_MRK = 0x010 # Marked format
|
90
92
|
X_SUP = 0x020 # Superscript
|
91
93
|
X_SUB = 0x040 # Subscript
|
92
|
-
|
93
|
-
|
94
|
+
X_COL = 0x080 # Coloured text
|
95
|
+
X_HRF = 0x100 # Link
|
94
96
|
|
95
97
|
# Formatting Masks
|
96
98
|
M_BLD = ~X_BLD
|
@@ -100,8 +102,8 @@ M_UND = ~X_UND
|
|
100
102
|
M_MRK = ~X_MRK
|
101
103
|
M_SUP = ~X_SUP
|
102
104
|
M_SUB = ~X_SUB
|
103
|
-
|
104
|
-
|
105
|
+
M_COL = ~X_COL
|
106
|
+
M_HRF = ~X_HRF
|
105
107
|
|
106
108
|
# ODT Styles
|
107
109
|
S_TITLE = "Title"
|
@@ -114,6 +116,7 @@ S_FIND = "First_20_line_20_indent"
|
|
114
116
|
S_TEXT = "Text_20_body"
|
115
117
|
S_META = "Text_20_Meta"
|
116
118
|
S_HNF = "Header_20_and_20_Footer"
|
119
|
+
S_NUM = "N0"
|
117
120
|
|
118
121
|
# Font Data
|
119
122
|
FONT_WEIGHT_NUM = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]
|
@@ -151,17 +154,15 @@ class ToOdt(Tokenizer):
|
|
151
154
|
|
152
155
|
self._mainPara: dict[str, ODTParagraphStyle] = {} # User-accessible paragraph styles
|
153
156
|
self._autoPara: dict[str, ODTParagraphStyle] = {} # Auto-generated paragraph styles
|
154
|
-
self._autoText: dict[
|
157
|
+
self._autoText: dict[str, ODTTextStyle] = {} # Auto-generated text styles
|
155
158
|
|
156
|
-
#
|
159
|
+
# Storage
|
157
160
|
self._nNote = 0
|
158
|
-
self._etNotes: dict[str, ET.Element] = {} # Generated note elements
|
159
|
-
|
160
161
|
self._errData = [] # List of errors encountered
|
161
162
|
|
162
163
|
# Properties
|
163
164
|
self._textFont = QFont("Liberation Serif", 12)
|
164
|
-
self.
|
165
|
+
self._headWeight = "bold"
|
165
166
|
self._headerFormat = ""
|
166
167
|
self._pageOffset = 0
|
167
168
|
|
@@ -183,7 +184,6 @@ class ToOdt(Tokenizer):
|
|
183
184
|
self._fLineHeight = "115%"
|
184
185
|
self._fBlockIndent = "1.693cm"
|
185
186
|
self._fTextIndent = "0.499cm"
|
186
|
-
self._textAlign = "left"
|
187
187
|
self._dLanguage = "en"
|
188
188
|
self._dCountry = "GB"
|
189
189
|
|
@@ -219,39 +219,14 @@ class ToOdt(Tokenizer):
|
|
219
219
|
self._mDocLeft = "2.000cm"
|
220
220
|
self._mDocRight = "2.000cm"
|
221
221
|
|
222
|
-
# Colour
|
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"
|
232
|
-
|
233
222
|
return
|
234
223
|
|
235
224
|
##
|
236
225
|
# Setters
|
237
226
|
##
|
238
227
|
|
239
|
-
def setLanguage(self, language: str | None) -> None:
|
240
|
-
"""Set language for the document."""
|
241
|
-
if language:
|
242
|
-
lang, _, country = language.partition("_")
|
243
|
-
self._dLanguage = lang or self._dLanguage
|
244
|
-
self._dCountry = country or self._dCountry
|
245
|
-
return
|
246
|
-
|
247
|
-
def setColourHeaders(self, state: bool) -> None:
|
248
|
-
"""Enable/disable coloured headings and comments."""
|
249
|
-
self._colourHead = state
|
250
|
-
return
|
251
|
-
|
252
228
|
def setPageLayout(
|
253
|
-
self, width:
|
254
|
-
top: int | float, bottom: int | float, left: int | float, right: int | float
|
229
|
+
self, width: float, height: float, top: float, bottom: float, left: float, right: float
|
255
230
|
) -> None:
|
256
231
|
"""Set the document page size and margins in millimetres."""
|
257
232
|
self._mDocWidth = f"{width/10.0:.3f}cm"
|
@@ -274,6 +249,8 @@ class ToOdt(Tokenizer):
|
|
274
249
|
|
275
250
|
def initDocument(self) -> None:
|
276
251
|
"""Initialises a new open document XML tree."""
|
252
|
+
super().initDocument()
|
253
|
+
|
277
254
|
# Initialise Variables
|
278
255
|
# ====================
|
279
256
|
|
@@ -281,21 +258,27 @@ class ToOdt(Tokenizer):
|
|
281
258
|
fontWeight = str(intWeight)
|
282
259
|
fontBold = str(min(intWeight + 300, 900))
|
283
260
|
|
261
|
+
lang, _, country = self._dLocale.name().partition("_")
|
262
|
+
self._dLanguage = lang or self._dLanguage
|
263
|
+
self._dCountry = country or self._dCountry
|
264
|
+
|
284
265
|
self._fontFamily = self._textFont.family()
|
285
266
|
self._fontSize = self._textFont.pointSize()
|
286
267
|
self._fontWeight = FONT_WEIGHT_MAP.get(fontWeight, fontWeight)
|
287
268
|
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
|
288
269
|
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable"
|
289
270
|
self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold)
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
self.
|
294
|
-
self.
|
295
|
-
self.
|
296
|
-
self.
|
297
|
-
self.
|
298
|
-
self.
|
271
|
+
self._headWeight = self._fontBold if self._boldHeads else None
|
272
|
+
|
273
|
+
hScale = self._scaleHeads
|
274
|
+
self._fSizeTitle = self._emToPt(nwStyles.H_SIZES[0] if hScale else 1.0) # Was 2.50
|
275
|
+
self._fSizeHead1 = self._emToPt(nwStyles.H_SIZES[1] if hScale else 1.0) # Was 2.00
|
276
|
+
self._fSizeHead2 = self._emToPt(nwStyles.H_SIZES[2] if hScale else 1.0) # Was 1.60
|
277
|
+
self._fSizeHead3 = self._emToPt(nwStyles.H_SIZES[3] if hScale else 1.0) # Was 1.30
|
278
|
+
self._fSizeHead4 = self._emToPt(nwStyles.H_SIZES[4] if hScale else 1.0) # Was 1.15
|
279
|
+
self._fSizeHead = self._emToPt(nwStyles.H_SIZES[4] if hScale else 1.0) # Was 1.15
|
280
|
+
self._fSizeText = self._emToPt(1.0)
|
281
|
+
self._fSizeFoot = self._emToPt(0.8)
|
299
282
|
|
300
283
|
mScale = self._lineHeight/1.15
|
301
284
|
|
@@ -322,22 +305,9 @@ class ToOdt(Tokenizer):
|
|
322
305
|
self._mLeftFoot = self._emToCm(self._marginFoot[0])
|
323
306
|
self._mBotFoot = self._emToCm(self._marginFoot[1])
|
324
307
|
|
325
|
-
if self._colourHead:
|
326
|
-
self._colHead12 = "#2a6099"
|
327
|
-
self._opaHead12 = "100%"
|
328
|
-
self._colHead34 = "#444444"
|
329
|
-
self._opaHead34 = "100%"
|
330
|
-
self._colMetaTx = "#813709"
|
331
|
-
self._opaMetaTx = "100%"
|
332
|
-
|
333
|
-
if self._showDialog:
|
334
|
-
self._colDialogM = "#2a6099"
|
335
|
-
self._colDialogA = "#813709"
|
336
|
-
|
337
308
|
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
|
338
309
|
self._fBlockIndent = self._emToCm(self._blockIndent)
|
339
310
|
self._fTextIndent = self._emToCm(self._firstWidth)
|
340
|
-
self._textAlign = "justify" if self._doJustify else "left"
|
341
311
|
|
342
312
|
# Clear Errors
|
343
313
|
self._errData = []
|
@@ -408,33 +378,21 @@ class ToOdt(Tokenizer):
|
|
408
378
|
timeStamp = datetime.now().isoformat(sep="T", timespec="seconds")
|
409
379
|
|
410
380
|
# Office Meta Data
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
xMeta.text = f"novelWriter/{__version__}"
|
416
|
-
|
417
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "initial-creator"))
|
418
|
-
xMeta.text = self._project.data.author
|
419
|
-
|
420
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("meta", "editing-cycles"))
|
421
|
-
xMeta.text = str(self._project.data.saveCount)
|
381
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "creation-date"), timeStamp)
|
382
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "generator"), f"novelWriter/{__version__}")
|
383
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "initial-creator"), self._project.data.author)
|
384
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "editing-cycles"), self._project.data.saveCount)
|
422
385
|
|
423
386
|
# Format is: PnYnMnDTnHnMnS
|
424
387
|
# https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#duration
|
425
388
|
eT = self._project.data.editTime
|
426
|
-
|
427
|
-
|
389
|
+
fT = f"P{eT//86400:d}DT{eT%86400//3600:d}H{eT%3600//60:d}M{eT%60:d}S"
|
390
|
+
xmlSubElem(self._xMeta, _mkTag("meta", "editing-duration"), fT)
|
428
391
|
|
429
392
|
# Dublin Core Meta Data
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "date"))
|
434
|
-
xMeta.text = timeStamp
|
435
|
-
|
436
|
-
xMeta = ET.SubElement(self._xMeta, _mkTag("dc", "creator"))
|
437
|
-
xMeta.text = self._project.data.author
|
393
|
+
xmlSubElem(self._xMeta, _mkTag("dc", "title"), self._project.data.name)
|
394
|
+
xmlSubElem(self._xMeta, _mkTag("dc", "date"), timeStamp)
|
395
|
+
xmlSubElem(self._xMeta, _mkTag("dc", "creator"), self._project.data.author)
|
438
396
|
|
439
397
|
self._pageStyles()
|
440
398
|
self._defaultStyles()
|
@@ -445,140 +403,130 @@ class ToOdt(Tokenizer):
|
|
445
403
|
|
446
404
|
def doConvert(self) -> None:
|
447
405
|
"""Convert the list of text tokens into XML elements."""
|
448
|
-
self._result = "" # Not used, but cleared just in case
|
449
|
-
|
450
406
|
xText = self._xText
|
451
|
-
for tType, _, tText, tFormat, tStyle in self.
|
407
|
+
for tType, _, tText, tFormat, tStyle in self._blocks:
|
452
408
|
|
453
409
|
# Styles
|
454
410
|
oStyle = ODTParagraphStyle("New")
|
455
|
-
if tStyle
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
oStyle.setMarginRight(self._fBlockIndent)
|
411
|
+
if tStyle & BlockFmt.LEFT:
|
412
|
+
oStyle.setTextAlign("left")
|
413
|
+
elif tStyle & BlockFmt.RIGHT:
|
414
|
+
oStyle.setTextAlign("right")
|
415
|
+
elif tStyle & BlockFmt.CENTRE:
|
416
|
+
oStyle.setTextAlign("center")
|
417
|
+
elif tStyle & BlockFmt.JUSTIFY:
|
418
|
+
oStyle.setTextAlign("justify")
|
419
|
+
|
420
|
+
if tStyle & BlockFmt.PBB:
|
421
|
+
oStyle.setBreakBefore("page")
|
422
|
+
if tStyle & BlockFmt.PBA:
|
423
|
+
oStyle.setBreakAfter("page")
|
424
|
+
|
425
|
+
if tStyle & BlockFmt.Z_BTM:
|
426
|
+
oStyle.setMarginBottom("0.000cm")
|
427
|
+
if tStyle & BlockFmt.Z_TOP:
|
428
|
+
oStyle.setMarginTop("0.000cm")
|
429
|
+
|
430
|
+
if tStyle & BlockFmt.IND_L:
|
431
|
+
oStyle.setMarginLeft(self._fBlockIndent)
|
432
|
+
if tStyle & BlockFmt.IND_R:
|
433
|
+
oStyle.setMarginRight(self._fBlockIndent)
|
479
434
|
|
480
435
|
# Process Text Types
|
481
|
-
if tType ==
|
436
|
+
if tType == BlockTyp.TEXT:
|
482
437
|
# Text indentation is processed here because there is a
|
483
438
|
# dedicated pre-defined style for it
|
484
|
-
if tStyle &
|
439
|
+
if tStyle & BlockFmt.IND_T:
|
485
440
|
self._addTextPar(xText, S_FIND, oStyle, tText, tFmt=tFormat)
|
486
441
|
else:
|
487
442
|
self._addTextPar(xText, S_TEXT, oStyle, tText, tFmt=tFormat)
|
488
443
|
|
489
|
-
elif tType ==
|
444
|
+
elif tType == BlockTyp.TITLE:
|
490
445
|
# Title must be text:p
|
491
|
-
|
492
|
-
self._addTextPar(xText, S_TITLE, oStyle, tHead, isHead=False)
|
446
|
+
self._addTextPar(xText, S_TITLE, oStyle, tText, isHead=False)
|
493
447
|
|
494
|
-
elif tType ==
|
495
|
-
|
496
|
-
self._addTextPar(xText, S_HEAD1, oStyle, tHead, isHead=True, oLevel="1")
|
448
|
+
elif tType == BlockTyp.HEAD1:
|
449
|
+
self._addTextPar(xText, S_HEAD1, oStyle, tText, isHead=True, oLevel="1")
|
497
450
|
|
498
|
-
elif tType ==
|
499
|
-
|
500
|
-
self._addTextPar(xText, S_HEAD2, oStyle, tHead, isHead=True, oLevel="2")
|
451
|
+
elif tType == BlockTyp.HEAD2:
|
452
|
+
self._addTextPar(xText, S_HEAD2, oStyle, tText, isHead=True, oLevel="2")
|
501
453
|
|
502
|
-
elif tType ==
|
503
|
-
|
504
|
-
self._addTextPar(xText, S_HEAD3, oStyle, tHead, isHead=True, oLevel="3")
|
454
|
+
elif tType == BlockTyp.HEAD3:
|
455
|
+
self._addTextPar(xText, S_HEAD3, oStyle, tText, isHead=True, oLevel="3")
|
505
456
|
|
506
|
-
elif tType ==
|
507
|
-
|
508
|
-
self._addTextPar(xText, S_HEAD4, oStyle, tHead, isHead=True, oLevel="4")
|
457
|
+
elif tType == BlockTyp.HEAD4:
|
458
|
+
self._addTextPar(xText, S_HEAD4, oStyle, tText, isHead=True, oLevel="4")
|
509
459
|
|
510
|
-
elif tType ==
|
460
|
+
elif tType == BlockTyp.SEP:
|
511
461
|
self._addTextPar(xText, S_SEP, oStyle, tText)
|
512
462
|
|
513
|
-
elif tType ==
|
463
|
+
elif tType == BlockTyp.SKIP:
|
514
464
|
self._addTextPar(xText, S_TEXT, oStyle, "")
|
515
465
|
|
516
|
-
elif tType ==
|
517
|
-
|
518
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
519
|
-
|
520
|
-
elif tType == self.T_SHORT and self._doSynopsis:
|
521
|
-
tTemp, tFmt = self._formatSynopsis(tText, tFormat, False)
|
522
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
466
|
+
elif tType == BlockTyp.COMMENT:
|
467
|
+
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
523
468
|
|
524
|
-
elif tType ==
|
525
|
-
|
526
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
527
|
-
|
528
|
-
elif tType == self.T_KEYWORD and self._doKeywords:
|
529
|
-
tTemp, tFmt = self._formatKeywords(tText)
|
530
|
-
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
469
|
+
elif tType == BlockTyp.KEYWORD:
|
470
|
+
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
531
471
|
|
532
472
|
return
|
533
473
|
|
534
474
|
def closeDocument(self) -> None:
|
535
|
-
"""
|
475
|
+
"""Add additional collected information to the XML."""
|
536
476
|
for style in self._autoPara.values():
|
537
477
|
style.packXML(self._xAuto)
|
538
478
|
for style in self._autoText.values():
|
539
479
|
style.packXML(self._xAuto)
|
480
|
+
if self._counts:
|
481
|
+
xFields = ET.Element(_mkTag("text", "user-field-decls"))
|
482
|
+
for key, value in self._counts.items():
|
483
|
+
ET.SubElement(xFields, _mkTag("text", "user-field-decl"), attrib={
|
484
|
+
_mkTag("office", "value-type"): "float",
|
485
|
+
_mkTag("office", "value"): str(value),
|
486
|
+
_mkTag("text", "name"): f"Manuscript{key[:1].upper()}{key[1:]}",
|
487
|
+
})
|
488
|
+
self._xText.insert(0, xFields)
|
540
489
|
return
|
541
490
|
|
542
|
-
def
|
543
|
-
"""Save the data to an .fodt file."""
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
logger.info("Wrote file: %s", path)
|
549
|
-
return
|
550
|
-
|
551
|
-
def saveOpenDocText(self, path: str | Path) -> None:
|
552
|
-
"""Save the data to an .odt file."""
|
553
|
-
mMani = _mkTag("manifest", "manifest")
|
554
|
-
mVers = _mkTag("manifest", "version")
|
555
|
-
mPath = _mkTag("manifest", "full-path")
|
556
|
-
mType = _mkTag("manifest", "media-type")
|
557
|
-
mFile = _mkTag("manifest", "file-entry")
|
558
|
-
|
559
|
-
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
|
560
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
|
561
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
|
562
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
|
563
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
|
564
|
-
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
|
565
|
-
|
566
|
-
oRoot = _mkTag("office", "document-settings")
|
567
|
-
oVers = _mkTag("office", "version")
|
568
|
-
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
|
569
|
-
|
570
|
-
def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
|
571
|
-
with zipObj.open(name, mode="w") as fObj:
|
572
|
-
xml = ET.ElementTree(xObj)
|
491
|
+
def saveDocument(self, path: Path) -> None:
|
492
|
+
"""Save the data to an .fodt or .odt file."""
|
493
|
+
if self._isFlat:
|
494
|
+
with open(path, mode="wb") as fObj:
|
495
|
+
xml = ET.ElementTree(self._dFlat)
|
496
|
+
xmlIndent(xml)
|
573
497
|
xml.write(fObj, encoding="utf-8", xml_declaration=True)
|
574
498
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
499
|
+
else:
|
500
|
+
mMani = _mkTag("manifest", "manifest")
|
501
|
+
mVers = _mkTag("manifest", "version")
|
502
|
+
mPath = _mkTag("manifest", "full-path")
|
503
|
+
mType = _mkTag("manifest", "media-type")
|
504
|
+
mFile = _mkTag("manifest", "file-entry")
|
505
|
+
|
506
|
+
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
|
507
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
|
508
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
|
509
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
|
510
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
|
511
|
+
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
|
512
|
+
|
513
|
+
oRoot = _mkTag("office", "document-settings")
|
514
|
+
oVers = _mkTag("office", "version")
|
515
|
+
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
|
516
|
+
|
517
|
+
def xmlToZip(name: str, root: ET.Element, zipObj: ZipFile) -> None:
|
518
|
+
zipObj.writestr(
|
519
|
+
name, ET.tostring(root, encoding="utf-8", xml_declaration=True),
|
520
|
+
compress_type=ZIP_DEFLATED, compresslevel=3,
|
521
|
+
)
|
522
|
+
|
523
|
+
with ZipFile(path, mode="w") as outZip:
|
524
|
+
outZip.writestr("mimetype", X_MIME, compress_type=None, compresslevel=None)
|
525
|
+
xmlToZip("META-INF/manifest.xml", xMani, outZip)
|
526
|
+
xmlToZip("settings.xml", xSett, outZip)
|
527
|
+
xmlToZip("content.xml", self._dCont, outZip)
|
528
|
+
xmlToZip("meta.xml", self._dMeta, outZip)
|
529
|
+
xmlToZip("styles.xml", self._dStyl, outZip)
|
582
530
|
|
583
531
|
logger.info("Wrote file: %s", path)
|
584
532
|
|
@@ -588,43 +536,14 @@ class ToOdt(Tokenizer):
|
|
588
536
|
# Internal Functions
|
589
537
|
##
|
590
538
|
|
591
|
-
def _formatSynopsis(self, text: str, fmt: T_Formats, synopsis: bool) -> tuple[str, T_Formats]:
|
592
|
-
"""Apply formatting to synopsis lines."""
|
593
|
-
name = self._localLookup("Synopsis" if synopsis else "Short Description")
|
594
|
-
shift = len(name) + 2
|
595
|
-
rTxt = f"{name}: {text}"
|
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)
|
598
|
-
return rTxt, rFmt
|
599
|
-
|
600
|
-
def _formatComments(self, text: str, fmt: T_Formats) -> tuple[str, T_Formats]:
|
601
|
-
"""Apply formatting to comments."""
|
602
|
-
name = self._localLookup("Comment")
|
603
|
-
shift = len(name) + 2
|
604
|
-
rTxt = f"{name}: {text}"
|
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)
|
607
|
-
return rTxt, rFmt
|
608
|
-
|
609
|
-
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
|
610
|
-
"""Apply formatting to keywords."""
|
611
|
-
valid, bits, _ = self._project.index.scanThis("@"+text)
|
612
|
-
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
613
|
-
return "", []
|
614
|
-
|
615
|
-
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
616
|
-
rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(rTxt) - 1, self.FMT_B_E, "")]
|
617
|
-
if len(bits) > 1:
|
618
|
-
if bits[0] == nwKeyWords.TAG_KEY:
|
619
|
-
rTxt += bits[1]
|
620
|
-
else:
|
621
|
-
rTxt += ", ".join(bits[1:])
|
622
|
-
|
623
|
-
return rTxt, rFmt
|
624
|
-
|
625
539
|
def _addTextPar(
|
626
|
-
self,
|
627
|
-
|
540
|
+
self,
|
541
|
+
xParent: ET.Element,
|
542
|
+
styleName: str, oStyle: ODTParagraphStyle,
|
543
|
+
tText: str,
|
544
|
+
tFmt: Sequence[tuple[int, int, str]] | None = None,
|
545
|
+
isHead: bool = False,
|
546
|
+
oLevel: str | None = None,
|
628
547
|
) -> None:
|
629
548
|
"""Add a text paragraph to the text XML element."""
|
630
549
|
tAttr = {_mkTag("text", "style-name"): self._paraStyle(styleName, oStyle)}
|
@@ -646,12 +565,13 @@ class ToOdt(Tokenizer):
|
|
646
565
|
|
647
566
|
parProc = XMLParagraph(xElem)
|
648
567
|
|
649
|
-
pErr = 0
|
650
568
|
xFmt = 0x00
|
651
569
|
tFrag = ""
|
652
570
|
fLast = 0
|
653
571
|
xNode = None
|
654
|
-
|
572
|
+
fClass = ""
|
573
|
+
fLink = ""
|
574
|
+
for fPos, fFmt, fData in tFmt or []:
|
655
575
|
|
656
576
|
# Add any extra nodes
|
657
577
|
if xNode is not None:
|
@@ -663,51 +583,55 @@ class ToOdt(Tokenizer):
|
|
663
583
|
if xFmt == 0x00:
|
664
584
|
parProc.appendText(tFrag)
|
665
585
|
else:
|
666
|
-
parProc.appendSpan(tFrag, self._textStyle(xFmt))
|
586
|
+
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass), fLink)
|
667
587
|
|
668
588
|
# Calculate the change of format
|
669
|
-
if fFmt ==
|
589
|
+
if fFmt == TextFmt.B_B:
|
670
590
|
xFmt |= X_BLD
|
671
|
-
elif fFmt ==
|
591
|
+
elif fFmt == TextFmt.B_E:
|
672
592
|
xFmt &= M_BLD
|
673
|
-
elif fFmt ==
|
593
|
+
elif fFmt == TextFmt.I_B:
|
674
594
|
xFmt |= X_ITA
|
675
|
-
elif fFmt ==
|
595
|
+
elif fFmt == TextFmt.I_E:
|
676
596
|
xFmt &= M_ITA
|
677
|
-
elif fFmt ==
|
597
|
+
elif fFmt == TextFmt.D_B:
|
678
598
|
xFmt |= X_DEL
|
679
|
-
elif fFmt ==
|
599
|
+
elif fFmt == TextFmt.D_E:
|
680
600
|
xFmt &= M_DEL
|
681
|
-
elif fFmt ==
|
601
|
+
elif fFmt == TextFmt.U_B:
|
682
602
|
xFmt |= X_UND
|
683
|
-
elif fFmt ==
|
603
|
+
elif fFmt == TextFmt.U_E:
|
684
604
|
xFmt &= M_UND
|
685
|
-
elif fFmt ==
|
605
|
+
elif fFmt == TextFmt.M_B:
|
686
606
|
xFmt |= X_MRK
|
687
|
-
elif fFmt ==
|
607
|
+
elif fFmt == TextFmt.M_E:
|
688
608
|
xFmt &= M_MRK
|
689
|
-
elif fFmt ==
|
609
|
+
elif fFmt == TextFmt.SUP_B:
|
690
610
|
xFmt |= X_SUP
|
691
|
-
elif fFmt ==
|
611
|
+
elif fFmt == TextFmt.SUP_E:
|
692
612
|
xFmt &= M_SUP
|
693
|
-
elif fFmt ==
|
613
|
+
elif fFmt == TextFmt.SUB_B:
|
694
614
|
xFmt |= X_SUB
|
695
|
-
elif fFmt ==
|
615
|
+
elif fFmt == TextFmt.SUB_E:
|
696
616
|
xFmt &= M_SUB
|
697
|
-
elif fFmt ==
|
698
|
-
xFmt |=
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
elif fFmt ==
|
704
|
-
xFmt
|
705
|
-
|
617
|
+
elif fFmt == TextFmt.COL_B:
|
618
|
+
xFmt |= X_COL
|
619
|
+
fClass = fData
|
620
|
+
elif fFmt == TextFmt.COL_E:
|
621
|
+
xFmt &= M_COL
|
622
|
+
fClass = ""
|
623
|
+
elif fFmt == TextFmt.HRF_B:
|
624
|
+
xFmt |= X_HRF
|
625
|
+
fLink = fData
|
626
|
+
elif fFmt == TextFmt.HRF_E:
|
627
|
+
xFmt &= M_HRF
|
628
|
+
fLink = ""
|
629
|
+
elif fFmt == TextFmt.FNOTE:
|
706
630
|
xNode = self._generateFootnote(fData)
|
707
|
-
elif fFmt ==
|
631
|
+
elif fFmt == TextFmt.FIELD:
|
632
|
+
xNode = self._generateField(fData, xFmt)
|
633
|
+
elif fFmt == TextFmt.STRIP:
|
708
634
|
pass
|
709
|
-
else:
|
710
|
-
pErr += 1
|
711
635
|
|
712
636
|
fLast = fPos
|
713
637
|
|
@@ -718,10 +642,7 @@ class ToOdt(Tokenizer):
|
|
718
642
|
if xFmt == 0x00:
|
719
643
|
parProc.appendText(tFrag)
|
720
644
|
else:
|
721
|
-
parProc.appendSpan(tFrag, self._textStyle(xFmt))
|
722
|
-
|
723
|
-
if pErr > 0:
|
724
|
-
self._errData.append("Unknown format tag encountered")
|
645
|
+
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass), fLink)
|
725
646
|
|
726
647
|
nErr, errMsg = parProc.checkError()
|
727
648
|
if nErr > 0: # pragma: no cover
|
@@ -752,10 +673,13 @@ class ToOdt(Tokenizer):
|
|
752
673
|
|
753
674
|
return modStyle.name
|
754
675
|
|
755
|
-
def _textStyle(self, hFmt: int) -> str:
|
676
|
+
def _textStyle(self, hFmt: int, fClass: str = "") -> str:
|
756
677
|
"""Return a text style for a given style code."""
|
757
|
-
|
758
|
-
|
678
|
+
tKey = str(hFmt)
|
679
|
+
if fClass and (color := self._classes.get(fClass)):
|
680
|
+
tKey = f"{tKey}:{fClass}"
|
681
|
+
if tKey in self._autoText:
|
682
|
+
return self._autoText[tKey].name
|
759
683
|
|
760
684
|
style = ODTTextStyle(f"T{len(self._autoText)+1:d}")
|
761
685
|
if hFmt & X_BLD:
|
@@ -768,18 +692,21 @@ class ToOdt(Tokenizer):
|
|
768
692
|
if hFmt & X_UND:
|
769
693
|
style.setUnderlineStyle("solid")
|
770
694
|
style.setUnderlineWidth("auto")
|
771
|
-
style.
|
695
|
+
style.setUnderlineColor("font-color")
|
772
696
|
if hFmt & X_MRK:
|
773
|
-
style.
|
697
|
+
style.setBackgroundColor(self._theme.highlight)
|
774
698
|
if hFmt & X_SUP:
|
775
699
|
style.setTextPosition("super")
|
776
700
|
if hFmt & X_SUB:
|
777
701
|
style.setTextPosition("sub")
|
778
|
-
if hFmt &
|
779
|
-
style.
|
780
|
-
if hFmt &
|
781
|
-
style.
|
782
|
-
|
702
|
+
if hFmt & X_COL and color:
|
703
|
+
style.setColor(color)
|
704
|
+
if hFmt & X_HRF:
|
705
|
+
style.setColor(self._theme.link)
|
706
|
+
style.setUnderlineStyle("solid")
|
707
|
+
style.setUnderlineWidth("auto")
|
708
|
+
style.setUnderlineColor("font-color")
|
709
|
+
self._autoText[tKey] = style
|
783
710
|
|
784
711
|
return style.name
|
785
712
|
|
@@ -788,21 +715,41 @@ class ToOdt(Tokenizer):
|
|
788
715
|
if content := self._footnotes.get(key):
|
789
716
|
self._nNote += 1
|
790
717
|
nStyle = ODTParagraphStyle("New")
|
791
|
-
xNote =
|
718
|
+
xNote = xmlElement(_mkTag("text", "note"), attrib={
|
792
719
|
_mkTag("text", "id"): f"ftn{self._nNote}",
|
793
720
|
_mkTag("text", "note-class"): "footnote",
|
794
721
|
})
|
795
|
-
|
796
|
-
|
797
|
-
xBody = ET.SubElement(xNote, _mkTag("text", "note-body"))
|
722
|
+
xmlSubElem(xNote, _mkTag("text", "note-citation"), self._nNote)
|
723
|
+
xBody = xmlSubElem(xNote, _mkTag("text", "note-body"))
|
798
724
|
self._addTextPar(xBody, "Footnote", nStyle, content[0], tFmt=content[1])
|
799
725
|
return xNote
|
800
726
|
return None
|
801
727
|
|
728
|
+
def _generateField(self, key: str, fmt: int) -> ET.Element | None:
|
729
|
+
"""Generate a data field XML object."""
|
730
|
+
if key and (field := key.partition(":")[2]):
|
731
|
+
xField = xmlElement(_mkTag("text", "user-field-get"), "0", tail="", attrib={
|
732
|
+
_mkTag("style", "data-style-name"): S_NUM,
|
733
|
+
_mkTag("text", "name"): f"Manuscript{field[:1].upper()}{field[1:]}",
|
734
|
+
})
|
735
|
+
if fmt == 0x00:
|
736
|
+
return xField
|
737
|
+
else:
|
738
|
+
xSpan = xmlElement(TAG_SPAN, "", tail="", attrib={
|
739
|
+
_mkTag("text", "style-name"): self._textStyle(fmt),
|
740
|
+
})
|
741
|
+
xSpan.append(xField)
|
742
|
+
return xSpan
|
743
|
+
return None
|
744
|
+
|
802
745
|
def _emToCm(self, value: float) -> str:
|
803
746
|
"""Converts an em value to centimetres."""
|
804
747
|
return f"{value*2.54/72*self._fontSize:.3f}cm"
|
805
748
|
|
749
|
+
def _emToPt(self, scale: float) -> str:
|
750
|
+
"""Compute relative font size in points."""
|
751
|
+
return f"{round(scale * self._fontSize):d}pt"
|
752
|
+
|
806
753
|
##
|
807
754
|
# Style Elements
|
808
755
|
##
|
@@ -898,10 +845,20 @@ class ToOdt(Tokenizer):
|
|
898
845
|
_mkTag("style", "class"): "extra",
|
899
846
|
})
|
900
847
|
|
848
|
+
# Numbers Style
|
849
|
+
xStyl = ET.SubElement(self._xStyl, _mkTag("number", "number-style"), attrib={
|
850
|
+
_mkTag("style", "name"): S_NUM,
|
851
|
+
})
|
852
|
+
ET.SubElement(xStyl, _mkTag("number", "number"), attrib={
|
853
|
+
_mkTag("number", "min-integer-digits"): "1",
|
854
|
+
})
|
855
|
+
|
901
856
|
return
|
902
857
|
|
903
858
|
def _useableStyles(self) -> None:
|
904
859
|
"""Set the usable styles."""
|
860
|
+
hColor = self._theme.head if self._colorHeads else None
|
861
|
+
|
905
862
|
# Add Text Body Style
|
906
863
|
style = ODTParagraphStyle(S_TEXT)
|
907
864
|
style.setDisplayName("Text body")
|
@@ -910,7 +867,7 @@ class ToOdt(Tokenizer):
|
|
910
867
|
style.setMarginTop(self._mTopText)
|
911
868
|
style.setMarginBottom(self._mBotText)
|
912
869
|
style.setLineHeight(self._fLineHeight)
|
913
|
-
style.setTextAlign(self.
|
870
|
+
style.setTextAlign(self._defaultAlign)
|
914
871
|
style.setFontName(self._fontFamily)
|
915
872
|
style.setFontFamily(self._fontFamily)
|
916
873
|
style.setFontSize(self._fSizeText)
|
@@ -939,8 +896,6 @@ class ToOdt(Tokenizer):
|
|
939
896
|
style.setFontFamily(self._fontFamily)
|
940
897
|
style.setFontSize(self._fSizeText)
|
941
898
|
style.setFontWeight(self._fontWeight)
|
942
|
-
style.setColour(self._colMetaTx)
|
943
|
-
style.setOpacity(self._opaMetaTx)
|
944
899
|
style.packXML(self._xStyl)
|
945
900
|
self._mainPara[style.name] = style
|
946
901
|
|
@@ -956,7 +911,7 @@ class ToOdt(Tokenizer):
|
|
956
911
|
style.setFontName(self._fontFamily)
|
957
912
|
style.setFontFamily(self._fontFamily)
|
958
913
|
style.setFontSize(self._fSizeTitle)
|
959
|
-
style.setFontWeight(self.
|
914
|
+
style.setFontWeight(self._headWeight)
|
960
915
|
style.packXML(self._xStyl)
|
961
916
|
self._mainPara[style.name] = style
|
962
917
|
|
@@ -989,9 +944,8 @@ class ToOdt(Tokenizer):
|
|
989
944
|
style.setFontName(self._fontFamily)
|
990
945
|
style.setFontFamily(self._fontFamily)
|
991
946
|
style.setFontSize(self._fSizeHead1)
|
992
|
-
style.setFontWeight(self.
|
993
|
-
style.
|
994
|
-
style.setOpacity(self._opaHead12)
|
947
|
+
style.setFontWeight(self._headWeight)
|
948
|
+
style.setColor(hColor)
|
995
949
|
style.packXML(self._xStyl)
|
996
950
|
self._mainPara[style.name] = style
|
997
951
|
|
@@ -1007,9 +961,8 @@ class ToOdt(Tokenizer):
|
|
1007
961
|
style.setFontName(self._fontFamily)
|
1008
962
|
style.setFontFamily(self._fontFamily)
|
1009
963
|
style.setFontSize(self._fSizeHead2)
|
1010
|
-
style.setFontWeight(self.
|
1011
|
-
style.
|
1012
|
-
style.setOpacity(self._opaHead12)
|
964
|
+
style.setFontWeight(self._headWeight)
|
965
|
+
style.setColor(hColor)
|
1013
966
|
style.packXML(self._xStyl)
|
1014
967
|
self._mainPara[style.name] = style
|
1015
968
|
|
@@ -1025,9 +978,8 @@ class ToOdt(Tokenizer):
|
|
1025
978
|
style.setFontName(self._fontFamily)
|
1026
979
|
style.setFontFamily(self._fontFamily)
|
1027
980
|
style.setFontSize(self._fSizeHead3)
|
1028
|
-
style.setFontWeight(self.
|
1029
|
-
style.
|
1030
|
-
style.setOpacity(self._opaHead34)
|
981
|
+
style.setFontWeight(self._headWeight)
|
982
|
+
style.setColor(hColor)
|
1031
983
|
style.packXML(self._xStyl)
|
1032
984
|
self._mainPara[style.name] = style
|
1033
985
|
|
@@ -1043,9 +995,8 @@ class ToOdt(Tokenizer):
|
|
1043
995
|
style.setFontName(self._fontFamily)
|
1044
996
|
style.setFontFamily(self._fontFamily)
|
1045
997
|
style.setFontSize(self._fSizeHead4)
|
1046
|
-
style.setFontWeight(self.
|
1047
|
-
style.
|
1048
|
-
style.setOpacity(self._opaHead34)
|
998
|
+
style.setFontWeight(self._headWeight)
|
999
|
+
style.setColor(hColor)
|
1049
1000
|
style.packXML(self._xStyl)
|
1050
1001
|
self._mainPara[style.name] = style
|
1051
1002
|
|
@@ -1080,12 +1031,12 @@ class ToOdt(Tokenizer):
|
|
1080
1031
|
|
1081
1032
|
# Standard Page Header
|
1082
1033
|
if self._headerFormat:
|
1083
|
-
pre, page, post = self._headerFormat.partition(nwHeadFmt.
|
1034
|
+
pre, page, post = self._headerFormat.partition(nwHeadFmt.DOC_PAGE)
|
1084
1035
|
|
1085
|
-
pre = pre.replace(nwHeadFmt.
|
1086
|
-
pre = pre.replace(nwHeadFmt.
|
1087
|
-
post = post.replace(nwHeadFmt.
|
1088
|
-
post = post.replace(nwHeadFmt.
|
1036
|
+
pre = pre.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name)
|
1037
|
+
pre = pre.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author)
|
1038
|
+
post = post.replace(nwHeadFmt.DOC_PROJECT, self._project.data.name)
|
1039
|
+
post = post.replace(nwHeadFmt.DOC_AUTHOR, self._project.data.author)
|
1089
1040
|
|
1090
1041
|
xHead = ET.SubElement(xPage, _mkTag("style", "header"))
|
1091
1042
|
xPar = ET.SubElement(xHead, _mkTag("text", "p"), attrib={
|
@@ -1167,16 +1118,6 @@ class ODTParagraphStyle:
|
|
1167
1118
|
def name(self) -> str:
|
1168
1119
|
return self._name
|
1169
1120
|
|
1170
|
-
##
|
1171
|
-
# Checkers
|
1172
|
-
##
|
1173
|
-
|
1174
|
-
def isUnaligned(self) -> bool:
|
1175
|
-
"""Check if paragraph has any sort of alignment or margins."""
|
1176
|
-
return all(
|
1177
|
-
self._pAttr[n][1] is None for n in ["text-align", "margin-left", "margin-right"]
|
1178
|
-
)
|
1179
|
-
|
1180
1121
|
##
|
1181
1122
|
# Setters
|
1182
1123
|
##
|
@@ -1306,14 +1247,14 @@ class ODTParagraphStyle:
|
|
1306
1247
|
self._tAttr["font-weight"][1] = None
|
1307
1248
|
return
|
1308
1249
|
|
1309
|
-
def
|
1250
|
+
def setColor(self, value: QColor | None) -> None:
|
1310
1251
|
"""Set text colour."""
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1252
|
+
if isinstance(value, QColor):
|
1253
|
+
self._tAttr["color"][1] = value.name(QtHexRgb)
|
1254
|
+
self._tAttr["opacity"][1] = f"{int(100.0 * value.alphaF())}%"
|
1255
|
+
else:
|
1256
|
+
self._tAttr["color"][1] = None
|
1257
|
+
self._tAttr["opacity"][1] = None
|
1317
1258
|
return
|
1318
1259
|
|
1319
1260
|
##
|
@@ -1414,18 +1355,18 @@ class ODTTextStyle:
|
|
1414
1355
|
self._tAttr["font-style"][1] = None
|
1415
1356
|
return
|
1416
1357
|
|
1417
|
-
def
|
1358
|
+
def setColor(self, value: QColor | None) -> None:
|
1418
1359
|
"""Set text colour."""
|
1419
|
-
if value
|
1420
|
-
self._tAttr["color"][1] = value
|
1360
|
+
if isinstance(value, QColor):
|
1361
|
+
self._tAttr["color"][1] = value.name(QtHexRgb)
|
1421
1362
|
else:
|
1422
1363
|
self._tAttr["color"][1] = None
|
1423
1364
|
return
|
1424
1365
|
|
1425
|
-
def
|
1366
|
+
def setBackgroundColor(self, value: QColor | None) -> None:
|
1426
1367
|
"""Set text background colour."""
|
1427
|
-
if value
|
1428
|
-
self._tAttr["background-color"][1] = value
|
1368
|
+
if isinstance(value, QColor):
|
1369
|
+
self._tAttr["background-color"][1] = value.name(QtHexRgb)
|
1429
1370
|
else:
|
1430
1371
|
self._tAttr["background-color"][1] = None
|
1431
1372
|
return
|
@@ -1470,7 +1411,7 @@ class ODTTextStyle:
|
|
1470
1411
|
self._tAttr["text-underline-width"][1] = None
|
1471
1412
|
return
|
1472
1413
|
|
1473
|
-
def
|
1414
|
+
def setUnderlineColor(self, value: str | None) -> None:
|
1474
1415
|
"""Set text underline colour."""
|
1475
1416
|
if value in self.VALID_LCOL:
|
1476
1417
|
self._tAttr["text-underline-color"][1] = value
|
@@ -1595,13 +1536,22 @@ class XMLParagraph:
|
|
1595
1536
|
|
1596
1537
|
return
|
1597
1538
|
|
1598
|
-
def appendSpan(self, text: str,
|
1539
|
+
def appendSpan(self, text: str, style: str, link: str) -> None:
|
1599
1540
|
"""Append a text span to the XML element. The span is always
|
1600
|
-
closed since we do not
|
1541
|
+
closed since we do not produce nested spans (like Libre Office).
|
1601
1542
|
Therefore we return to the root element level when we're done
|
1602
1543
|
processing the text of the span.
|
1603
1544
|
"""
|
1604
|
-
|
1545
|
+
if link:
|
1546
|
+
self._xTail = ET.SubElement(self._xRoot, _mkTag("text", "a"), attrib={
|
1547
|
+
_mkTag("xlink", "type"): "simple",
|
1548
|
+
_mkTag("xlink", "href"): link,
|
1549
|
+
_mkTag("text", "style-name"): style,
|
1550
|
+
})
|
1551
|
+
else:
|
1552
|
+
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={
|
1553
|
+
_mkTag("text", "style-name"): style,
|
1554
|
+
})
|
1605
1555
|
self._xTail.text = "" # Defaults to None
|
1606
1556
|
self._xTail.tail = "" # Defaults to None
|
1607
1557
|
self._nState = X_SPAN_TEXT
|