From 717aac9dc8ca7b55633014faf9e08482395af1a7 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 18 Oct 2024 21:18:59 +0200 Subject: [PATCH] Add remaining code blocks, and add colours --- novelwriter/formats/todocx.py | 143 ++++++++++++++++++++++++------- novelwriter/formats/tokenizer.py | 2 - novelwriter/formats/toodt.py | 19 ++-- 3 files changed, 123 insertions(+), 41 deletions(-) diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py index 6e88b30b..6b7b2819 100644 --- a/novelwriter/formats/todocx.py +++ b/novelwriter/formats/todocx.py @@ -1,9 +1,9 @@ """ -novelWriter – DOCX Text Converter +novelWriter – DocX Text Converter ================================= File History: -Created: 2024-10-15 [2.6b1] ToRaw +Created: 2024-10-18 [2.6b1] ToDocX This file is a part of novelWriter Copyright 2018–2024, Veronica Berglyd Olsen @@ -32,7 +32,7 @@ from zipfile import ZipFile from novelwriter import __version__ from novelwriter.common import xmlIndent -from novelwriter.constants import nwHeadFmt, nwStyles +from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles from novelwriter.core.project import NWProject from novelwriter.formats.tokenizer import T_Formats, Tokenizer @@ -110,6 +110,15 @@ M_DLG = ~X_DLG M_DLA = ~X_DLA +# Colours +COL_HEAD_L12 = "2a6099" +COL_HEAD_L34 = "444444" +COL_DIALOG_M = "2a6099" +COL_DIALOG_A = "813709" +COL_META_TXT = "813709" +COL_MARK_TXT = "ffffa6" + + class ToDocX(Tokenizer): """Core: DocX Document Writer @@ -243,27 +252,27 @@ class ToDocX(Tokenizer): tHead = tText.replace(nwHeadFmt.BR, "\n") self._addFragments(par, "Heading4", tHead, tFormat) - # elif tType == self.T_SEP: - # self._addTextPar(xText, S_SEP, oStyle, tText) + elif tType == self.T_SEP: + self._addFragments(par, "Separator", tText) - # elif tType == self.T_SKIP: - # self._addTextPar(xText, S_TEXT, oStyle, "") + elif tType == self.T_SKIP: + self._addFragments(par, "Normal", "") - # elif tType == self.T_SYNOPSIS and self._doSynopsis: - # tTemp, tFmt = self._formatSynopsis(tText, tFormat, True) - # self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt) + elif tType == self.T_SYNOPSIS and self._doSynopsis: + tTemp, tFmt = self._formatSynopsis(tText, tFormat, True) + self._addFragments(par, "TextMeta", tTemp, tFmt) - # elif tType == self.T_SHORT and self._doSynopsis: - # tTemp, tFmt = self._formatSynopsis(tText, tFormat, False) - # self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt) + elif tType == self.T_SHORT and self._doSynopsis: + tTemp, tFmt = self._formatSynopsis(tText, tFormat, False) + self._addFragments(par, "TextMeta", tTemp, tFmt) - # elif tType == self.T_COMMENT and self._doComments: - # tTemp, tFmt = self._formatComments(tText, tFormat) - # self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt) + elif tType == self.T_COMMENT and self._doComments: + tTemp, tFmt = self._formatComments(tText, tFormat) + self._addFragments(par, "TextMeta", tTemp, tFmt) - # elif tType == self.T_KEYWORD and self._doKeywords: - # tTemp, tFmt = self._formatKeywords(tText) - # self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt) + elif tType == self.T_KEYWORD and self._doKeywords: + tTemp, tFmt = self._formatKeywords(tText) + self._addFragments(par, "TextMeta", tTemp, tFmt) par.finalise(self._xBody) @@ -370,12 +379,48 @@ class ToDocX(Tokenizer): # Internal Functions ## - def _addFragments(self, par: DocXParagraph, pStyle: str, text: str, tFmt: T_Formats) -> None: + def _formatSynopsis(self, text: str, fmt: T_Formats, synopsis: bool) -> tuple[str, T_Formats]: + """Apply formatting to synopsis lines.""" + name = self._localLookup("Synopsis" if synopsis else "Short Description") + shift = len(name) + 2 + rTxt = f"{name}: {text}" + rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")] + rFmt.extend((p + shift, f, d) for p, f, d in fmt) + return rTxt, rFmt + + def _formatComments(self, text: str, fmt: T_Formats) -> tuple[str, T_Formats]: + """Apply formatting to comments.""" + name = self._localLookup("Comment") + shift = len(name) + 2 + rTxt = f"{name}: {text}" + rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(name) + 1, self.FMT_B_E, "")] + rFmt.extend((p + shift, f, d) for p, f, d in fmt) + return rTxt, rFmt + + def _formatKeywords(self, text: str) -> tuple[str, T_Formats]: + """Apply formatting to keywords.""" + valid, bits, _ = self._project.index.scanThis("@"+text) + if not valid or not bits or bits[0] not in nwLabels.KEY_NAME: + return "", [] + + rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: " + rFmt: T_Formats = [(0, self.FMT_B_B, ""), (len(rTxt) - 1, self.FMT_B_E, "")] + if len(bits) > 1: + if bits[0] == nwKeyWords.TAG_KEY: + rTxt += bits[1] + else: + rTxt += ", ".join(bits[1:]) + + return rTxt, rFmt + + def _addFragments( + self, par: DocXParagraph, pStyle: str, text: str, tFmt: T_Formats | None = None + ) -> None: """Apply formatting tags to text.""" par.setStyle(pStyle) xFmt = 0x00 fStart = 0 - for fPos, fFmt, fData in tFmt: + for fPos, fFmt, fData in tFmt or []: run = DocXRun(text[fStart:fPos], xFmt) par.addRun(run) @@ -456,12 +501,12 @@ class ToDocX(Tokenizer): def _useableStyles(self) -> None: """Set the usable styles.""" hScale = self._scaleHeads + hColor = self._colorHeads # Add Normal Style self._addParStyle( name="Normal", styleId="Normal", - size=1.0, default=True, margins=self._marginText, ) @@ -486,6 +531,7 @@ class ToDocX(Tokenizer): nextStyle="Normal", margins=self._marginHead1, level=0, + color=COL_HEAD_L12 if hColor else None, ) # Add Heading 2 @@ -497,6 +543,7 @@ class ToDocX(Tokenizer): nextStyle="Normal", margins=self._marginHead2, level=1, + color=COL_HEAD_L12 if hColor else None, ) # Add Heading 3 @@ -508,6 +555,7 @@ class ToDocX(Tokenizer): nextStyle="Normal", margins=self._marginHead3, level=1, + color=COL_HEAD_L34 if hColor else None, ) # Add Heading 4 @@ -519,6 +567,27 @@ class ToDocX(Tokenizer): nextStyle="Normal", margins=self._marginHead4, level=1, + color=COL_HEAD_L34 if hColor else None, + ) + + # Add Separator + self._addParStyle( + name="Separator", + styleId="Separator", + basedOn="Normal", + nextStyle="Normal", + margins=self._marginSep, + align="center", + ) + + # Add Text Meta Style + self._addParStyle( + name="Text Meta", + styleId="TextMeta", + basedOn="Normal", + nextStyle="Normal", + margins=self._marginMeta, + color=COL_META_TXT, ) return @@ -527,12 +596,14 @@ class ToDocX(Tokenizer): self, *, name: str, styleId: str, - size: float, + size: float = 1.0, basedOn: str | None = None, nextStyle: str | None = None, margins: tuple[float, float] | None = None, + align: str | None = None, default: bool = False, level: int | None = None, + color: str | None = None, ) -> None: """Add a paragraph style.""" sAttr = {} @@ -553,17 +624,21 @@ class ToDocX(Tokenizer): if level is not None: ET.SubElement(xStyl, _wTag("outlineLvl"), attrib={_wTag("val"): str(level)}) - xPPr = ET.SubElement(xStyl, _wTag("pPr")) + pPr = ET.SubElement(xStyl, _wTag("pPr")) if margins: - ET.SubElement(xPPr, _wTag("spacing"), attrib={ + ET.SubElement(pPr, _wTag("spacing"), attrib={ _wTag("before"): str(int(20.0 * margins[0] * self._fontSize)), _wTag("after"): str(int(20.0 * margins[1] * self._fontSize)), _wTag("line"): ln, }) + if align: + ET.SubElement(pPr, _wTag("jc"), attrib={_wTag("val"): align}) - xRPr = ET.SubElement(xStyl, _wTag("rPr")) - ET.SubElement(xRPr, _wTag("sz"), attrib={_wTag("val"): sz}) - ET.SubElement(xRPr, _wTag("szCs"), attrib={_wTag("val"): sz}) + rPr = ET.SubElement(xStyl, _wTag("rPr")) + ET.SubElement(rPr, _wTag("sz"), attrib={_wTag("val"): sz}) + ET.SubElement(rPr, _wTag("szCs"), attrib={_wTag("val"): sz}) + if color: + ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): color}) return @@ -630,9 +705,9 @@ class DocXParagraph: # Values spacing = {} - if self._topMargin: + if self._topMargin is not None: spacing["before"] = str(self._topMargin) - if self._bottomMargin: + if self._bottomMargin is not None: spacing["after"] = str(self._bottomMargin) # Paragraph @@ -672,12 +747,20 @@ class DocXRun: ET.SubElement(rPr, _wTag("i")) if fmt & X_UND == X_UND: ET.SubElement(rPr, _wTag("u"), attrib={_wTag("val"): "single"}) + if fmt & X_MRK == X_MRK: + ET.SubElement(rPr, _wTag("shd"), attrib={ + _wTag("fill"): COL_MARK_TXT, _wTag("val"): "clear", + }) if fmt & X_DEL == X_DEL: ET.SubElement(rPr, _wTag("strike")) if fmt & X_SUP == X_SUP: ET.SubElement(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "superscript"}) if fmt & X_SUB == X_SUB: ET.SubElement(rPr, _wTag("vertAlign"), attrib={_wTag("val"): "subscript"}) + if fmt & X_DLG == X_DLG: + ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_M}) + if fmt & X_DLA == X_DLA: + ET.SubElement(rPr, _wTag("color"), attrib={_wTag("val"): COL_DIALOG_A}) temp = text while (parts := temp.partition("\n"))[0]: diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index e2c5817f..916afab2 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -205,7 +205,6 @@ class Tokenizer(ABC): self._hFormatter = HeadingFormatter(self._project) self._noSep = True # Flag to indicate that we don't want a scene separator self._noIndent = False # Flag to disable text indent on next paragraph - self._showDialog = False # Flag for dialogue highlighting # This File self._isNovel = False # Document is a novel document @@ -380,7 +379,6 @@ class Tokenizer(ABC): def setDialogueHighlight(self, state: bool) -> None: """Enable or disable dialogue highlighting.""" self._rxDialogue = [] - self._showDialog = state if state: if CONFIG.dialogStyle > 0: self._rxDialogue.append(( diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py index 5e68afdc..fd683fd9 100644 --- a/novelwriter/formats/toodt.py +++ b/novelwriter/formats/toodt.py @@ -237,8 +237,8 @@ class ToOdt(Tokenizer): self._opaHead12 = None self._colHead34 = None self._opaHead34 = None - self._colDialogM = None - self._colDialogA = None + self._colDialogM = "#2a6099" + self._colDialogA = "#813709" self._colMetaTx = "#813709" self._opaMetaTx = "100%" self._markText = "#ffffa6" @@ -337,10 +337,6 @@ class ToOdt(Tokenizer): self._colHead34 = "#444444" self._opaHead34 = "100%" - if self._showDialog: - self._colDialogM = "#2a6099" - self._colDialogA = "#813709" - self._fLineHeight = f"{round(100 * self._lineHeight):d}%" self._fBlockIndent = self._emToCm(self._blockIndent) self._fTextIndent = self._emToCm(self._firstWidth) @@ -625,8 +621,13 @@ class ToOdt(Tokenizer): return rTxt, rFmt def _addTextPar( - self, xParent: ET.Element, styleName: str, oStyle: ODTParagraphStyle, tText: str, - tFmt: Sequence[tuple[int, int, str]] = [], isHead: bool = False, oLevel: str | None = None + self, + xParent: ET.Element, + styleName: str, oStyle: ODTParagraphStyle, + tText: str, + tFmt: Sequence[tuple[int, int, str]] | None = None, + isHead: bool = False, + oLevel: str | None = None, ) -> None: """Add a text paragraph to the text XML element.""" tAttr = {_mkTag("text", "style-name"): self._paraStyle(styleName, oStyle)} @@ -653,7 +654,7 @@ class ToOdt(Tokenizer): tFrag = "" fLast = 0 xNode = None - for fPos, fFmt, fData in tFmt: + for fPos, fFmt, fData in tFmt or []: # Add any extra nodes if xNode is not None: