novelWriter 2.1.1__py3-none-any.whl → 2.2rc1__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.1.1.dist-info → novelWriter-2.2rc1.dist-info}/METADATA +3 -3
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/RECORD +105 -76
- novelwriter/__init__.py +6 -24
- novelwriter/assets/i18n/project_de_DE.json +10 -0
- novelwriter/assets/i18n/project_en_GB.json +11 -0
- novelwriter/assets/i18n/project_en_US.json +10 -0
- novelwriter/assets/i18n/project_ja_JP.json +11 -1
- novelwriter/assets/i18n/project_nb_NO.json +10 -0
- novelwriter/assets/i18n/project_nn_NO.json +10 -0
- novelwriter/assets/icons/novelwriter.ico +0 -0
- novelwriter/assets/icons/novelwriter.svg +8 -183
- novelwriter/assets/icons/typicons_dark/icons.conf +17 -2
- novelwriter/assets/icons/typicons_dark/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_dark/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +17 -2
- novelwriter/assets/icons/typicons_light/nw_deco-h2-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h3-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-h4-narrow.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_deco-note.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_panel.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +8 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +5 -0
- novelwriter/assets/icons/typicons_light/typ_eye.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-menu.svg +4 -0
- novelwriter/assets/icons/x-novelwriter-project.ico +0 -0
- novelwriter/assets/icons/x-novelwriter-project.svg +7 -206
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +7 -0
- novelwriter/assets/text/release_notes.htm +7 -37
- novelwriter/common.py +22 -1
- novelwriter/config.py +27 -42
- novelwriter/constants.py +45 -7
- novelwriter/core/buildsettings.py +40 -24
- novelwriter/core/coretools.py +8 -1
- novelwriter/core/docbuild.py +2 -6
- novelwriter/core/index.py +264 -175
- novelwriter/core/options.py +8 -3
- novelwriter/core/project.py +2 -2
- novelwriter/core/projectdata.py +3 -3
- novelwriter/core/tohtml.py +60 -59
- novelwriter/core/tokenizer.py +110 -70
- novelwriter/core/tomd.py +51 -38
- novelwriter/core/toodt.py +184 -147
- novelwriter/dialogs/preferences.py +75 -106
- novelwriter/dialogs/projsettings.py +101 -110
- novelwriter/dialogs/updates.py +25 -14
- novelwriter/enum.py +28 -3
- novelwriter/extensions/novelselector.py +1 -1
- novelwriter/gui/doceditor.py +1345 -1235
- novelwriter/gui/dochighlight.py +98 -62
- novelwriter/gui/docviewer.py +151 -340
- novelwriter/gui/docviewerpanel.py +457 -0
- novelwriter/gui/editordocument.py +126 -0
- novelwriter/gui/mainmenu.py +350 -300
- novelwriter/gui/noveltree.py +101 -125
- novelwriter/gui/outline.py +154 -171
- novelwriter/gui/projtree.py +480 -380
- novelwriter/gui/sidebar.py +106 -75
- novelwriter/gui/statusbar.py +1 -1
- novelwriter/gui/theme.py +114 -75
- novelwriter/guimain.py +353 -254
- novelwriter/shared.py +36 -3
- novelwriter/tools/dictionaries.py +268 -0
- novelwriter/tools/manusbuild.py +17 -6
- novelwriter/tools/manuscript.py +11 -3
- novelwriter/tools/manussettings.py +0 -14
- novelwriter/tools/projwizard.py +16 -2
- novelwriter/tools/writingstats.py +1 -1
- novelwriter/assets/icons/typicons_dark/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_th-menu.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_at.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_th-menu.svg +0 -4
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/WHEEL +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.1.1.dist-info → novelWriter-2.2rc1.dist-info}/top_level.txt +0 -0
novelwriter/core/toodt.py
CHANGED
@@ -27,10 +27,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
27
27
|
from __future__ import annotations
|
28
28
|
|
29
29
|
import logging
|
30
|
-
from pathlib import Path
|
31
30
|
import xml.etree.ElementTree as ET
|
32
31
|
|
32
|
+
from typing import Sequence
|
33
33
|
from hashlib import sha256
|
34
|
+
from pathlib import Path
|
34
35
|
from zipfile import ZipFile
|
35
36
|
from datetime import datetime
|
36
37
|
|
@@ -82,11 +83,17 @@ TAG_STNM = _mkTag("text", "style-name")
|
|
82
83
|
X_BLD = 0x01 # Bold format
|
83
84
|
X_ITA = 0x02 # Italic format
|
84
85
|
X_DEL = 0x04 # Strikethrough format
|
86
|
+
X_UND = 0x08 # Underline format
|
87
|
+
X_SUP = 0x10 # Superscript
|
88
|
+
X_SUB = 0x20 # Subscript
|
85
89
|
|
86
90
|
# Formatting Masks
|
87
91
|
M_BLD = ~X_BLD
|
88
92
|
M_ITA = ~X_ITA
|
89
93
|
M_DEL = ~X_DEL
|
94
|
+
M_UND = ~X_UND
|
95
|
+
M_SUP = ~X_SUP
|
96
|
+
M_SUB = ~X_SUB
|
90
97
|
|
91
98
|
|
92
99
|
class ToOdt(Tokenizer):
|
@@ -188,7 +195,7 @@ class ToOdt(Tokenizer):
|
|
188
195
|
# Setters
|
189
196
|
##
|
190
197
|
|
191
|
-
def setLanguage(self, language: str) -> None:
|
198
|
+
def setLanguage(self, language: str | None) -> None:
|
192
199
|
"""Set language for the document."""
|
193
200
|
if language:
|
194
201
|
langBits = language.split("_")
|
@@ -385,18 +392,9 @@ class ToOdt(Tokenizer):
|
|
385
392
|
"""Convert the list of text tokens into XML elements."""
|
386
393
|
self._result = "" # Not used, but cleared just in case
|
387
394
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
self.FMT_I_B: "I", # Italic open format
|
392
|
-
self.FMT_I_E: "i", # Italic close format
|
393
|
-
self.FMT_D_B: "_S", # Strikethrough open format
|
394
|
-
self.FMT_D_E: "s_", # Strikethrough close format
|
395
|
-
}
|
396
|
-
|
397
|
-
thisPar = []
|
398
|
-
thisFmt = []
|
399
|
-
parStyle = None
|
395
|
+
pFmt = []
|
396
|
+
pText = []
|
397
|
+
pStyle = None
|
400
398
|
for tType, _, tText, tFormat, tStyle in self._tokens:
|
401
399
|
|
402
400
|
# Styles
|
@@ -429,20 +427,22 @@ class ToOdt(Tokenizer):
|
|
429
427
|
|
430
428
|
# Process Text Types
|
431
429
|
if tType == self.T_EMPTY:
|
432
|
-
if len(
|
430
|
+
if len(pText) > 1 and pStyle is not None:
|
433
431
|
if self._doJustify:
|
434
|
-
|
432
|
+
pStyle.setTextAlign("left")
|
435
433
|
|
436
|
-
if len(
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
434
|
+
if len(pText) > 0 and pStyle is not None:
|
435
|
+
tTxt = ""
|
436
|
+
tFmt = []
|
437
|
+
for nText, nFmt in zip(pText, pFmt):
|
438
|
+
tLen = len(tTxt)
|
439
|
+
tTxt += f"{nText}\n"
|
440
|
+
tFmt.extend((p+tLen, fmt) for p, fmt in nFmt)
|
441
|
+
self._addTextPar("Text_20_body", pStyle, tTxt.rstrip(), tFmt=tFmt)
|
442
442
|
|
443
|
-
|
444
|
-
|
445
|
-
|
443
|
+
pFmt = []
|
444
|
+
pText = []
|
445
|
+
pStyle = None
|
446
446
|
|
447
447
|
elif tType == self.T_TITLE:
|
448
448
|
tHead = tText.replace(nwHeadFmt.BR, "\n")
|
@@ -475,20 +475,17 @@ class ToOdt(Tokenizer):
|
|
475
475
|
self._addTextPar("Separator", oStyle, "")
|
476
476
|
|
477
477
|
elif tType == self.T_TEXT:
|
478
|
-
if
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
for xPos, xLen, xFmt in tFormat:
|
483
|
-
tFmt = tFmt[:xPos] + odtTags[xFmt] + tFmt[xPos+xLen:]
|
484
|
-
|
485
|
-
tTxt = tText.rstrip()
|
486
|
-
tFmt = tFmt[:len(tTxt)]
|
487
|
-
thisPar.append(tTxt)
|
488
|
-
thisFmt.append(tFmt)
|
478
|
+
if pStyle is None:
|
479
|
+
pStyle = oStyle
|
480
|
+
pText.append(tText)
|
481
|
+
pFmt.append(tFormat)
|
489
482
|
|
490
483
|
elif tType == self.T_SYNOPSIS and self._doSynopsis:
|
491
|
-
tTemp, fTemp = self._formatSynopsis(tText)
|
484
|
+
tTemp, fTemp = self._formatSynopsis(tText, True)
|
485
|
+
self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
|
486
|
+
|
487
|
+
elif tType == self.T_SHORT and self._doSynopsis:
|
488
|
+
tTemp, fTemp = self._formatSynopsis(tText, False)
|
492
489
|
self._addTextPar("Text_20_Meta", oStyle, tTemp, tFmt=fTemp)
|
493
490
|
|
494
491
|
elif tType == self.T_COMMENT and self._doComments:
|
@@ -501,8 +498,8 @@ class ToOdt(Tokenizer):
|
|
501
498
|
|
502
499
|
return
|
503
500
|
|
504
|
-
def closeDocument(self):
|
505
|
-
"""
|
501
|
+
def closeDocument(self) -> None:
|
502
|
+
"""Pack the styles of the XML document."""
|
506
503
|
# Build the auto-generated styles
|
507
504
|
for styleName, styleObj in self._autoPara.values():
|
508
505
|
styleObj.packXML(self._xAuto, styleName)
|
@@ -510,7 +507,7 @@ class ToOdt(Tokenizer):
|
|
510
507
|
styleObj.packXML(self._xAuto, styleName)
|
511
508
|
return
|
512
509
|
|
513
|
-
def saveFlatXML(self, path: str | Path):
|
510
|
+
def saveFlatXML(self, path: str | Path) -> None:
|
514
511
|
"""Save the data to an .fodt file."""
|
515
512
|
with open(path, mode="wb") as fObj:
|
516
513
|
xml = ET.ElementTree(self._dFlat)
|
@@ -519,7 +516,7 @@ class ToOdt(Tokenizer):
|
|
519
516
|
logger.info("Wrote file: %s", path)
|
520
517
|
return
|
521
518
|
|
522
|
-
def saveOpenDocText(self, path: str | Path):
|
519
|
+
def saveOpenDocText(self, path: str | Path) -> None:
|
523
520
|
"""Save the data to an .odt file."""
|
524
521
|
mMani = _mkTag("manifest", "manifest")
|
525
522
|
mVers = _mkTag("manifest", "version")
|
@@ -559,46 +556,42 @@ class ToOdt(Tokenizer):
|
|
559
556
|
# Internal Functions
|
560
557
|
##
|
561
558
|
|
562
|
-
def _formatSynopsis(self, text: str) -> tuple[str,
|
559
|
+
def _formatSynopsis(self, text: str, synopsis: bool) -> tuple[str, list[tuple[int, int]]]:
|
563
560
|
"""Apply formatting to synopsis lines."""
|
564
|
-
|
565
|
-
|
566
|
-
|
561
|
+
if synopsis:
|
562
|
+
name = self._localLookup("Synopsis")
|
563
|
+
else:
|
564
|
+
name = self._localLookup("Short Description")
|
565
|
+
rTxt = f"{name}: {text}"
|
566
|
+
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
|
567
567
|
return rTxt, rFmt
|
568
568
|
|
569
|
-
def _formatComments(self, text: str) -> tuple[str,
|
569
|
+
def _formatComments(self, text: str) -> tuple[str, list[tuple[int, int]]]:
|
570
570
|
"""Apply formatting to comments."""
|
571
|
-
|
572
|
-
rTxt = "
|
573
|
-
rFmt =
|
571
|
+
name = self._localLookup("Comment")
|
572
|
+
rTxt = f"{name}: {text}"
|
573
|
+
rFmt = [(0, self.FMT_B_B), (len(name) + 1, self.FMT_B_E)]
|
574
574
|
return rTxt, rFmt
|
575
575
|
|
576
|
-
def _formatKeywords(self, text: str) -> tuple[str,
|
576
|
+
def _formatKeywords(self, text: str) -> tuple[str, list[tuple[int, int]]]:
|
577
577
|
"""Apply formatting to keywords."""
|
578
578
|
valid, bits, _ = self._project.index.scanThis("@"+text)
|
579
|
-
if not valid or not bits:
|
580
|
-
return "",
|
581
|
-
|
582
|
-
rTxt = ""
|
583
|
-
rFmt =
|
584
|
-
if bits
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
if bits[0] == nwKeyWords.TAG_KEY:
|
590
|
-
rTxt += bits[1]
|
591
|
-
rFmt += " "*len(bits[1])
|
592
|
-
else:
|
593
|
-
tTags = ", ".join(bits[1:])
|
594
|
-
rTxt += tTags
|
595
|
-
rFmt += (" "*len(tTags))
|
579
|
+
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
580
|
+
return "", []
|
581
|
+
|
582
|
+
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
583
|
+
rFmt = [(0, self.FMT_B_B), (len(rTxt) - 1, self.FMT_B_E)]
|
584
|
+
if len(bits) > 1:
|
585
|
+
if bits[0] == nwKeyWords.TAG_KEY:
|
586
|
+
rTxt += bits[1]
|
587
|
+
else:
|
588
|
+
rTxt += ", ".join(bits[1:])
|
596
589
|
|
597
590
|
return rTxt, rFmt
|
598
591
|
|
599
592
|
def _addTextPar(
|
600
|
-
self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
|
601
|
-
isHead: bool = False, oLevel: str | None = None
|
593
|
+
self, styleName: str, oStyle: ODTParagraphStyle, tText: str,
|
594
|
+
tFmt: Sequence[tuple[int, int]] = [], isHead: bool = False, oLevel: str | None = None
|
602
595
|
) -> None:
|
603
596
|
"""Add a text paragraph to the text XML element."""
|
604
597
|
tAttr = {}
|
@@ -616,58 +609,59 @@ class ToOdt(Tokenizer):
|
|
616
609
|
if not tText:
|
617
610
|
return
|
618
611
|
|
619
|
-
|
620
|
-
#
|
621
|
-
##
|
622
|
-
|
623
|
-
if len(tText) != len(tFmt):
|
624
|
-
# Generate an empty format if there isn't any or it doesn't match
|
625
|
-
tFmt = " "*len(tText)
|
626
|
-
|
627
|
-
# The formatting loop
|
628
|
-
tTemp = ""
|
629
|
-
xFmt = 0x00
|
630
|
-
pFmt = 0x00
|
631
|
-
pErr = 0
|
612
|
+
# Loop Over Fragments
|
613
|
+
# ===================
|
632
614
|
|
633
615
|
parProc = XMLParagraph(xElem)
|
634
616
|
|
635
|
-
|
617
|
+
pErr = 0
|
618
|
+
xFmt = 0x00
|
619
|
+
tFrag = ""
|
620
|
+
fLast = 0
|
621
|
+
for fPos, fFmt in tFmt:
|
622
|
+
|
623
|
+
# Add the text up to the current fragment
|
624
|
+
if tFrag := tText[fLast:fPos]:
|
625
|
+
if xFmt == 0x00:
|
626
|
+
parProc.appendText(tFrag)
|
627
|
+
else:
|
628
|
+
parProc.appendSpan(tFrag, self._textStyle(xFmt))
|
636
629
|
|
637
|
-
|
638
|
-
|
639
|
-
elif tFmt[i] == "_":
|
640
|
-
continue
|
641
|
-
elif tFmt[i] == "B":
|
630
|
+
# Calculate the change of format
|
631
|
+
if fFmt == self.FMT_B_B:
|
642
632
|
xFmt |= X_BLD
|
643
|
-
elif
|
633
|
+
elif fFmt == self.FMT_B_E:
|
644
634
|
xFmt &= M_BLD
|
645
|
-
elif
|
635
|
+
elif fFmt == self.FMT_I_B:
|
646
636
|
xFmt |= X_ITA
|
647
|
-
elif
|
637
|
+
elif fFmt == self.FMT_I_E:
|
648
638
|
xFmt &= M_ITA
|
649
|
-
elif
|
639
|
+
elif fFmt == self.FMT_D_B:
|
650
640
|
xFmt |= X_DEL
|
651
|
-
elif
|
641
|
+
elif fFmt == self.FMT_D_E:
|
652
642
|
xFmt &= M_DEL
|
643
|
+
elif fFmt == self.FMT_U_B:
|
644
|
+
xFmt |= X_UND
|
645
|
+
elif fFmt == self.FMT_U_E:
|
646
|
+
xFmt &= M_UND
|
647
|
+
elif fFmt == self.FMT_SUP_B:
|
648
|
+
xFmt |= X_SUP
|
649
|
+
elif fFmt == self.FMT_SUP_E:
|
650
|
+
xFmt &= M_SUP
|
651
|
+
elif fFmt == self.FMT_SUB_B:
|
652
|
+
xFmt |= X_SUB
|
653
|
+
elif fFmt == self.FMT_SUB_E:
|
654
|
+
xFmt &= M_SUB
|
653
655
|
else:
|
654
656
|
pErr += 1
|
655
657
|
|
656
|
-
|
657
|
-
if pFmt == 0x00:
|
658
|
-
parProc.appendText(tTemp)
|
659
|
-
tTemp = ""
|
660
|
-
else:
|
661
|
-
parProc.appendSpan(tTemp, self._textStyle(pFmt))
|
662
|
-
tTemp = ""
|
663
|
-
|
664
|
-
pFmt = xFmt
|
658
|
+
fLast = fPos
|
665
659
|
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
660
|
+
if tFrag := tText[fLast:]:
|
661
|
+
if xFmt == 0x00:
|
662
|
+
parProc.appendText(tFrag)
|
663
|
+
else:
|
664
|
+
parProc.appendSpan(tFrag, self._textStyle(xFmt))
|
671
665
|
|
672
666
|
if pErr > 0:
|
673
667
|
self._errData.append("Unknown format tag encountered")
|
@@ -713,14 +707,22 @@ class ToOdt(Tokenizer):
|
|
713
707
|
if hFmt & X_DEL:
|
714
708
|
newStyle.setStrikeStyle("solid")
|
715
709
|
newStyle.setStrikeType("single")
|
710
|
+
if hFmt & X_UND:
|
711
|
+
newStyle.setUnderlineStyle("solid")
|
712
|
+
newStyle.setUnderlineWidth("auto")
|
713
|
+
newStyle.setUnderlineColour("font-color")
|
714
|
+
if hFmt & X_SUP:
|
715
|
+
newStyle.setTextPosition("super")
|
716
|
+
if hFmt & X_SUB:
|
717
|
+
newStyle.setTextPosition("sub")
|
716
718
|
|
717
719
|
self._autoText[hFmt] = (newName, newStyle)
|
718
720
|
|
719
721
|
return newName
|
720
722
|
|
721
|
-
def _emToCm(self,
|
723
|
+
def _emToCm(self, value: float) -> str:
|
722
724
|
"""Converts an em value to centimetres."""
|
723
|
-
return f"{
|
725
|
+
return f"{value*2.54/72*self._textSize:.3f}cm"
|
724
726
|
|
725
727
|
##
|
726
728
|
# Style Elements
|
@@ -1193,56 +1195,56 @@ class ODTParagraphStyle:
|
|
1193
1195
|
# Methods
|
1194
1196
|
##
|
1195
1197
|
|
1196
|
-
def checkNew(self,
|
1198
|
+
def checkNew(self, style: ODTParagraphStyle) -> bool:
|
1197
1199
|
"""Check if there are new settings in refStyle that differ from
|
1198
1200
|
those in the current object.
|
1199
1201
|
"""
|
1200
|
-
for
|
1201
|
-
if aVal is not None and aVal != self._mAttr[
|
1202
|
+
for name, (_, aVal) in style._mAttr.items():
|
1203
|
+
if aVal is not None and aVal != self._mAttr[name][1]:
|
1202
1204
|
return True
|
1203
|
-
for
|
1204
|
-
if aVal is not None and aVal != self._pAttr[
|
1205
|
+
for name, (_, aVal) in style._pAttr.items():
|
1206
|
+
if aVal is not None and aVal != self._pAttr[name][1]:
|
1205
1207
|
return True
|
1206
|
-
for
|
1207
|
-
if aVal is not None and aVal != self._tAttr[
|
1208
|
+
for name, (_, aVal) in style._tAttr.items():
|
1209
|
+
if aVal is not None and aVal != self._tAttr[name][1]:
|
1208
1210
|
return True
|
1209
1211
|
return False
|
1210
1212
|
|
1211
1213
|
def getID(self) -> str:
|
1212
1214
|
"""Generate a unique ID from the settings."""
|
1213
|
-
|
1215
|
+
string = (
|
1214
1216
|
f"Paragraph:Main:{str(self._mAttr)}:"
|
1215
1217
|
f"Paragraph:Para:{str(self._pAttr)}:"
|
1216
1218
|
f"Paragraph:Text:{str(self._tAttr)}:"
|
1217
1219
|
)
|
1218
|
-
return sha256(
|
1220
|
+
return sha256(string.encode()).hexdigest()
|
1219
1221
|
|
1220
1222
|
def packXML(self, xParent: ET.Element, name: str) -> None:
|
1221
1223
|
"""Pack the content into an xml element."""
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1224
|
+
attr = {}
|
1225
|
+
attr[_mkTag("style", "name")] = name
|
1226
|
+
attr[_mkTag("style", "family")] = "paragraph"
|
1225
1227
|
for aName, (aNm, aVal) in self._mAttr.items():
|
1226
1228
|
if aVal is not None:
|
1227
|
-
|
1229
|
+
attr[_mkTag(aNm, aName)] = aVal
|
1228
1230
|
|
1229
|
-
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=
|
1231
|
+
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=attr)
|
1230
1232
|
|
1231
|
-
|
1233
|
+
attr = {}
|
1232
1234
|
for aName, (aNm, aVal) in self._pAttr.items():
|
1233
1235
|
if aVal is not None:
|
1234
|
-
|
1236
|
+
attr[_mkTag(aNm, aName)] = aVal
|
1235
1237
|
|
1236
|
-
if
|
1237
|
-
ET.SubElement(xEntry, _mkTag("style", "paragraph-properties"), attrib=
|
1238
|
+
if attr:
|
1239
|
+
ET.SubElement(xEntry, _mkTag("style", "paragraph-properties"), attrib=attr)
|
1238
1240
|
|
1239
|
-
|
1241
|
+
attr = {}
|
1240
1242
|
for aName, (aNm, aVal) in self._tAttr.items():
|
1241
1243
|
if aVal is not None:
|
1242
|
-
|
1244
|
+
attr[_mkTag(aNm, aName)] = aVal
|
1243
1245
|
|
1244
|
-
if
|
1245
|
-
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=
|
1246
|
+
if attr:
|
1247
|
+
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
|
1246
1248
|
|
1247
1249
|
return
|
1248
1250
|
|
@@ -1256,16 +1258,23 @@ class ODTTextStyle:
|
|
1256
1258
|
"""
|
1257
1259
|
VALID_WEIGHT = ["normal", "inherit", "bold"]
|
1258
1260
|
VALID_STYLE = ["normal", "inherit", "italic"]
|
1261
|
+
VALID_POS = ["super", "sub"]
|
1259
1262
|
VALID_LSTYLE = ["none", "solid"]
|
1260
|
-
VALID_LTYPE = ["
|
1263
|
+
VALID_LTYPE = ["single", "double"]
|
1264
|
+
VALID_LWIDTH = ["auto"]
|
1265
|
+
VALID_LCOL = ["font-color"]
|
1261
1266
|
|
1262
1267
|
def __init__(self) -> None:
|
1263
1268
|
# Text Attributes
|
1264
1269
|
self._tAttr = {
|
1265
1270
|
"font-weight": ["fo", None],
|
1266
1271
|
"font-style": ["fo", None],
|
1272
|
+
"text-position": ["style", None],
|
1267
1273
|
"text-line-through-style": ["style", None],
|
1268
1274
|
"text-line-through-type": ["style", None],
|
1275
|
+
"text-underline-style": ["style", None],
|
1276
|
+
"text-underline-width": ["style", None],
|
1277
|
+
"text-underline-color": ["style", None],
|
1269
1278
|
}
|
1270
1279
|
return
|
1271
1280
|
|
@@ -1287,6 +1296,13 @@ class ODTTextStyle:
|
|
1287
1296
|
self._tAttr["font-style"][1] = None
|
1288
1297
|
return
|
1289
1298
|
|
1299
|
+
def setTextPosition(self, value: str | None) -> None:
|
1300
|
+
if value in self.VALID_POS:
|
1301
|
+
self._tAttr["text-position"][1] = f"{value} 58%"
|
1302
|
+
else:
|
1303
|
+
self._tAttr["text-position"][1] = None
|
1304
|
+
return
|
1305
|
+
|
1290
1306
|
def setStrikeStyle(self, value: str | None) -> None:
|
1291
1307
|
if value in self.VALID_LSTYLE:
|
1292
1308
|
self._tAttr["text-line-through-style"][1] = value
|
@@ -1301,24 +1317,45 @@ class ODTTextStyle:
|
|
1301
1317
|
self._tAttr["text-line-through-type"][1] = None
|
1302
1318
|
return
|
1303
1319
|
|
1320
|
+
def setUnderlineStyle(self, value: str | None) -> None:
|
1321
|
+
if value in self.VALID_LSTYLE:
|
1322
|
+
self._tAttr["text-underline-style"][1] = value
|
1323
|
+
else:
|
1324
|
+
self._tAttr["text-underline-style"][1] = None
|
1325
|
+
return
|
1326
|
+
|
1327
|
+
def setUnderlineWidth(self, value: str | None) -> None:
|
1328
|
+
if value in self.VALID_LWIDTH:
|
1329
|
+
self._tAttr["text-underline-width"][1] = value
|
1330
|
+
else:
|
1331
|
+
self._tAttr["text-underline-width"][1] = None
|
1332
|
+
return
|
1333
|
+
|
1334
|
+
def setUnderlineColour(self, value: str | None) -> None:
|
1335
|
+
if value in self.VALID_LCOL:
|
1336
|
+
self._tAttr["text-underline-color"][1] = value
|
1337
|
+
else:
|
1338
|
+
self._tAttr["text-underline-color"][1] = None
|
1339
|
+
return
|
1340
|
+
|
1304
1341
|
##
|
1305
1342
|
# Methods
|
1306
1343
|
##
|
1307
1344
|
|
1308
1345
|
def packXML(self, xParent: ET.Element, name: str) -> None:
|
1309
1346
|
"""Pack the content into an xml element."""
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=
|
1347
|
+
attr = {}
|
1348
|
+
attr[_mkTag("style", "name")] = name
|
1349
|
+
attr[_mkTag("style", "family")] = "text"
|
1350
|
+
xEntry = ET.SubElement(xParent, _mkTag("style", "style"), attrib=attr)
|
1314
1351
|
|
1315
|
-
|
1352
|
+
attr = {}
|
1316
1353
|
for aName, (aNm, aVal) in self._tAttr.items():
|
1317
1354
|
if aVal is not None:
|
1318
|
-
|
1355
|
+
attr[_mkTag(aNm, aName)] = aVal
|
1319
1356
|
|
1320
|
-
if
|
1321
|
-
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=
|
1357
|
+
if attr:
|
1358
|
+
ET.SubElement(xEntry, _mkTag("style", "text-properties"), attrib=attr)
|
1322
1359
|
|
1323
1360
|
return
|
1324
1361
|
|
@@ -1368,17 +1405,17 @@ class XMLParagraph:
|
|
1368
1405
|
|
1369
1406
|
return
|
1370
1407
|
|
1371
|
-
def appendText(self,
|
1408
|
+
def appendText(self, text: str) -> None:
|
1372
1409
|
"""Append text to the XML element. We do this one character at
|
1373
1410
|
the time in order to be able to process line breaks, tabs and
|
1374
1411
|
spaces separately. Multiple spaces are concatenated into a
|
1375
1412
|
single tag, and must therefore be processed separately.
|
1376
1413
|
"""
|
1377
|
-
|
1414
|
+
text = stripEscape(text)
|
1378
1415
|
nSpaces = 0
|
1379
|
-
self._rawTxt +=
|
1416
|
+
self._rawTxt += text
|
1380
1417
|
|
1381
|
-
for c in
|
1418
|
+
for c in text:
|
1382
1419
|
if c == " ":
|
1383
1420
|
nSpaces += 1
|
1384
1421
|
continue
|
@@ -1433,17 +1470,17 @@ class XMLParagraph:
|
|
1433
1470
|
|
1434
1471
|
return
|
1435
1472
|
|
1436
|
-
def appendSpan(self,
|
1473
|
+
def appendSpan(self, text: str, fmt: str) -> None:
|
1437
1474
|
"""Append a text span to the XML element. The span is always
|
1438
1475
|
closed since we do not allow nested spans (like Libre Office).
|
1439
1476
|
Therefore we return to the root element level when we're done
|
1440
1477
|
processing the text of the span.
|
1441
1478
|
"""
|
1442
|
-
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={TAG_STNM:
|
1479
|
+
self._xTail = ET.SubElement(self._xRoot, TAG_SPAN, attrib={TAG_STNM: fmt})
|
1443
1480
|
self._xTail.text = "" # Defaults to None
|
1444
1481
|
self._xTail.tail = "" # Defaults to None
|
1445
1482
|
self._nState = X_SPAN_TEXT
|
1446
|
-
self.appendText(
|
1483
|
+
self.appendText(text)
|
1447
1484
|
self._nState = X_ROOT_TAIL
|
1448
1485
|
return
|
1449
1486
|
|