From e759aa8d1b03312ddbc1216f7138e7dc88e5ec44 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:07:57 +0200 Subject: [PATCH] Move colour theme to Tokenizer class --- novelwriter/formats/shared.py | 19 ++++++++ novelwriter/formats/todocx.py | 19 +++++--- novelwriter/formats/tohtml.py | 42 +++++++++--------- novelwriter/formats/tokenizer.py | 12 +++++- novelwriter/formats/toodt.py | 43 +++++++------------ novelwriter/formats/toqdoc.py | 23 +--------- novelwriter/gui/docviewer.py | 3 +- novelwriter/types.py | 4 ++ .../fmtToDocX_SaveDocument_styles.xml | 8 ++-- .../reference/fmtToOdt_SaveFlat_document.fodt | 12 +++--- tests/reference/fmtToOdt_SaveFull_styles.xml | 8 ++-- .../mBuildDocBuild_HTML5_Lorem_Ipsum.htm | 10 ++--- .../mBuildDocBuild_HTML5_Lorem_Ipsum.json | 14 +++--- ...uildDocBuild_OpenDocument_Lorem_Ipsum.fodt | 12 +++--- tests/test_formats/test_fmt_toodt.py | 18 +++++--- tests/test_formats/test_fmt_toqdoc.py | 4 +- 16 files changed, 132 insertions(+), 119 deletions(-) diff --git a/novelwriter/formats/shared.py b/novelwriter/formats/shared.py index 82a7c2c4..0c8fb701 100644 --- a/novelwriter/formats/shared.py +++ b/novelwriter/formats/shared.py @@ -29,6 +29,8 @@ import re from enum import Flag, IntEnum +from PyQt5.QtGui import QColor + ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""} RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL) @@ -40,6 +42,23 @@ def stripEscape(text: str) -> str: return text +class TextDocumentTheme: + """Default document theme.""" + + text: QColor = QColor(0, 0, 0) + highlight: QColor = QColor(255, 255, 166) + head: QColor = QColor(66, 113, 174) + comment: QColor = QColor(100, 100, 100) + note: QColor = QColor(129, 55, 9) + code: QColor = QColor(66, 113, 174) + modifier: QColor = QColor(129, 55, 9) + keyword: QColor = QColor(245, 135, 31) + tag: QColor = QColor(66, 113, 174) + optional: QColor = QColor(66, 113, 174) + dialog: QColor = QColor(66, 113, 174) + altdialog: QColor = QColor(129, 55, 9) + + # Enums # ===== diff --git a/novelwriter/formats/todocx.py b/novelwriter/formats/todocx.py index 1b5a0c80..e5b4c0a6 100644 --- a/novelwriter/formats/todocx.py +++ b/novelwriter/formats/todocx.py @@ -34,6 +34,7 @@ from typing import NamedTuple from zipfile import ZIP_DEFLATED, ZipFile from PyQt5.QtCore import QMarginsF, QSizeF +from PyQt5.QtGui import QColor from novelwriter import __version__ from novelwriter.common import firstFloat, xmlSubElem @@ -41,6 +42,7 @@ from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles from novelwriter.core.project import NWProject from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt from novelwriter.formats.tokenizer import Tokenizer +from novelwriter.types import QtHexRgb logger = logging.getLogger(__name__) @@ -80,6 +82,11 @@ def _mkTag(ns: str, tag: str) -> str: return tag +def _docXCol(color: QColor) -> str: + """Format a QColor as the DocX accepted value.""" + return color.name(QtHexRgb).lstrip("#") + + # Formatting Codes X_BLD = 0x001 # Bold format X_ITA = 0x002 # Italic format @@ -115,8 +122,6 @@ S_HEAD = "Header" S_FNOTE = "FootnoteText" # Colours -COL_HEAD_L12 = "2a6099" -COL_HEAD_L34 = "444444" COL_DIALOG_M = "2a6099" COL_DIALOG_A = "813709" COL_META_TXT = "813709" @@ -535,7 +540,7 @@ class ToDocX(Tokenizer): styles: list[DocXParStyle] = [] hScale = self._scaleHeads - hColor = self._colorHeads + hColor = _docXCol(self._theme.head) if self._colorHeads else None fSz = self._fontSize fnSz = 0.8 * self._fontSize fSz0 = (nwStyles.H_SIZES[0] * fSz) if hScale else fSz @@ -582,7 +587,7 @@ class ToDocX(Tokenizer): after=fSz * self._marginHead1[1], line=fSz1 * self._lineHeight, level=0, - color=COL_HEAD_L12 if hColor else None, + color=hColor, bold=self._boldHeads, )) @@ -597,7 +602,7 @@ class ToDocX(Tokenizer): after=fSz * self._marginHead2[1], line=fSz2 * self._lineHeight, level=1, - color=COL_HEAD_L12 if hColor else None, + color=hColor, bold=self._boldHeads, )) @@ -612,7 +617,7 @@ class ToDocX(Tokenizer): after=fSz * self._marginHead3[1], line=fSz3 * self._lineHeight, level=1, - color=COL_HEAD_L34 if hColor else None, + color=hColor, bold=self._boldHeads, )) @@ -627,7 +632,7 @@ class ToDocX(Tokenizer): after=fSz * self._marginHead4[1], line=fSz4 * self._lineHeight, level=1, - color=COL_HEAD_L34 if hColor else None, + color=hColor, bold=self._boldHeads, )) diff --git a/novelwriter/formats/tohtml.py b/novelwriter/formats/tohtml.py index 41449fc3..b00a95b8 100644 --- a/novelwriter/formats/tohtml.py +++ b/novelwriter/formats/tohtml.py @@ -34,7 +34,7 @@ from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels from novelwriter.core.project import NWProject from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape from novelwriter.formats.tokenizer import Tokenizer -from novelwriter.types import FONT_STYLE, FONT_WEIGHTS +from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb logger = logging.getLogger(__name__) @@ -354,16 +354,18 @@ class ToHtml(Tokenizer): return [] mScale = self._lineHeight/1.15 + tColor = self._theme.text.name(QtHexRgb) + hColor = self._theme.head.name(QtHexRgb) if self._colorHeads else tColor styles = [] font = self._textFont styles.append(( "body {{" - "font-family: '{0:s}'; font-size: {1:d}pt; " - "font-weight: {2:d}; font-style: {3:s};" + "color: {0:s}; font-family: '{1:s}'; font-size: {2:d}pt; " + "font-weight: {3:d}; font-style: {4:s};" "}}" ).format( - font.family(), font.pointSize(), + tColor, font.family(), font.pointSize(), FONT_WEIGHTS.get(font.weight(), 400), FONT_STYLE.get(font.style(), "normal"), )) @@ -380,43 +382,43 @@ class ToHtml(Tokenizer): )) styles.append(( "h1 {{" - "color: rgb(66, 113, 174); " + "color: {0:s}; " "page-break-after: avoid; " - "margin-top: {0:.2f}em; " - "margin-bottom: {1:.2f}em;" + "margin-top: {1:.2f}em; " + "margin-bottom: {2:.2f}em;" "}}" ).format( - mScale * self._marginHead1[0], mScale * self._marginHead1[1] + hColor, mScale * self._marginHead1[0], mScale * self._marginHead1[1] )) styles.append(( "h2 {{" - "color: rgb(66, 113, 174); " + "color: {0:s}; " "page-break-after: avoid; " - "margin-top: {0:.2f}em; " - "margin-bottom: {1:.2f}em;" + "margin-top: {1:.2f}em; " + "margin-bottom: {2:.2f}em;" "}}" ).format( - mScale * self._marginHead2[0], mScale * self._marginHead2[1] + hColor, mScale * self._marginHead2[0], mScale * self._marginHead2[1] )) styles.append(( "h3 {{" - "color: rgb(50, 50, 50); " + "color: {0:s}; " "page-break-after: avoid; " - "margin-top: {0:.2f}em; " - "margin-bottom: {1:.2f}em;" + "margin-top: {1:.2f}em; " + "margin-bottom: {2:.2f}em;" "}}" ).format( - mScale * self._marginHead3[0], mScale * self._marginHead3[1] + hColor, mScale * self._marginHead3[0], mScale * self._marginHead3[1] )) styles.append(( "h4 {{" - "color: rgb(50, 50, 50); " + "color: {0:s}; " "page-break-after: avoid; " - "margin-top: {0:.2f}em; " - "margin-bottom: {1:.2f}em;" + "margin-top: {1:.2f}em; " + "margin-bottom: {2:.2f}em;" "}}" ).format( - mScale * self._marginHead4[0], mScale * self._marginHead4[1] + hColor, mScale * self._marginHead4[0], mScale * self._marginHead4[1] )) styles.append(( ".title {{" diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index 7dd0a1f8..74f271b5 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -44,7 +44,9 @@ from novelwriter.constants import ( from novelwriter.core.index import processComment from novelwriter.core.project import NWProject from novelwriter.enum import nwComment, nwItemLayout -from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextFmt +from novelwriter.formats.shared import ( + BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt +) from novelwriter.text.patterns import REGEX_PATTERNS logger = logging.getLogger(__name__) @@ -110,6 +112,9 @@ class Tokenizer(ABC): self._keepBreaks = True # Keep line breaks in paragraphs self._defaultAlign = "left" # The default text alignment + # Other Setting + self._theme = TextDocumentTheme() + # Margins self._marginTitle = nwStyles.T_MARGIN["H0"] self._marginHead1 = nwStyles.T_MARGIN["H1"] @@ -218,6 +223,11 @@ class Tokenizer(ABC): # Setters ## + def setTheme(self, theme: TextDocumentTheme) -> None: + """Set the document colour theme.""" + self._theme = theme + return + def setPartitionFormat(self, hFormat: str, hide: bool = False) -> None: """Set the partition format pattern.""" self._fmtPart = hFormat.strip() diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py index d5bde589..9f680814 100644 --- a/novelwriter/formats/toodt.py +++ b/novelwriter/formats/toodt.py @@ -35,7 +35,7 @@ from hashlib import sha256 from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile -from PyQt5.QtGui import QFont +from PyQt5.QtGui import QColor, QFont from novelwriter import __version__ from novelwriter.common import xmlIndent, xmlSubElem @@ -220,10 +220,6 @@ class ToOdt(Tokenizer): self._mDocRight = "2.000cm" # Colour - self._colHead12 = None - self._opaHead12 = None - self._colHead34 = None - self._opaHead34 = None self._colDialogM = "#2a6099" self._colDialogA = "#813709" self._colMetaTx = "#813709" @@ -318,12 +314,6 @@ class ToOdt(Tokenizer): self._mLeftFoot = self._emToCm(self._marginFoot[0]) self._mBotFoot = self._emToCm(self._marginFoot[1]) - if self._colorHeads: - self._colHead12 = "#2a6099" - self._opaHead12 = "100%" - self._colHead34 = "#444444" - self._opaHead34 = "100%" - self._fLineHeight = f"{round(100 * self._lineHeight):d}%" self._fBlockIndent = self._emToCm(self._blockIndent) self._fTextIndent = self._emToCm(self._firstWidth) @@ -894,6 +884,8 @@ class ToOdt(Tokenizer): def _useableStyles(self) -> None: """Set the usable styles.""" + hColor = self._theme.head if self._colorHeads else None + # Add Text Body Style style = ODTParagraphStyle(S_TEXT) style.setDisplayName("Text body") @@ -931,8 +923,7 @@ class ToOdt(Tokenizer): style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeText) style.setFontWeight(self._fontWeight) - style.setColour(self._colMetaTx) - style.setOpacity(self._opaMetaTx) + style.setColour(self._theme.note) style.packXML(self._xStyl) self._mainPara[style.name] = style @@ -982,8 +973,7 @@ class ToOdt(Tokenizer): style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead1) style.setFontWeight(self._headWeight) - style.setColour(self._colHead12) - style.setOpacity(self._opaHead12) + style.setColour(hColor) style.packXML(self._xStyl) self._mainPara[style.name] = style @@ -1000,8 +990,7 @@ class ToOdt(Tokenizer): style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead2) style.setFontWeight(self._headWeight) - style.setColour(self._colHead12) - style.setOpacity(self._opaHead12) + style.setColour(hColor) style.packXML(self._xStyl) self._mainPara[style.name] = style @@ -1018,8 +1007,7 @@ class ToOdt(Tokenizer): style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead3) style.setFontWeight(self._headWeight) - style.setColour(self._colHead34) - style.setOpacity(self._opaHead34) + style.setColour(hColor) style.packXML(self._xStyl) self._mainPara[style.name] = style @@ -1036,8 +1024,7 @@ class ToOdt(Tokenizer): style.setFontFamily(self._fontFamily) style.setFontSize(self._fSizeHead4) style.setFontWeight(self._headWeight) - style.setColour(self._colHead34) - style.setOpacity(self._opaHead34) + style.setColour(hColor) style.packXML(self._xStyl) self._mainPara[style.name] = style @@ -1298,14 +1285,14 @@ class ODTParagraphStyle: self._tAttr["font-weight"][1] = None return - def setColour(self, value: str | None) -> None: + def setColour(self, value: QColor | None) -> None: """Set text colour.""" - self._tAttr["color"][1] = value - return - - def setOpacity(self, value: str | None) -> None: - """Set text opacity.""" - self._tAttr["opacity"][1] = value + if isinstance(value, QColor): + self._tAttr["color"][1] = value.name(QColor.NameFormat.HexRgb) + self._tAttr["opacity"][1] = f"{int(100.0 * value.alphaF())}%" + else: + self._tAttr["color"][1] = None + self._tAttr["opacity"][1] = None return ## diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py index 243059ed..1a48a562 100644 --- a/novelwriter/formats/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -29,7 +29,7 @@ from pathlib import Path from PyQt5.QtCore import QMarginsF, QSizeF from PyQt5.QtGui import ( - QColor, QFont, QFontMetricsF, QPageSize, QTextBlockFormat, QTextCharFormat, + QFont, QFontMetricsF, QPageSize, QTextBlockFormat, QTextCharFormat, QTextCursor, QTextDocument ) from PyQt5.QtPrintSupport import QPrinter @@ -49,21 +49,6 @@ logger = logging.getLogger(__name__) T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat] -class TextDocumentTheme: - text: QColor = QColor(0, 0, 0) - highlight: QColor = QColor(255, 255, 166) - head: QColor = QColor(66, 113, 174) - comment: QColor = QColor(100, 100, 100) - note: QColor = QColor(129, 55, 9) - code: QColor = QColor(66, 113, 174) - modifier: QColor = QColor(129, 55, 9) - keyword: QColor = QColor(245, 135, 31) - tag: QColor = QColor(66, 113, 174) - optional: QColor = QColor(66, 113, 174) - dialog: QColor = QColor(66, 113, 174) - altdialog: QColor = QColor(129, 55, 9) - - def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None: if cursor.position() > 0: cursor.insertBlock(bFmt) @@ -84,7 +69,6 @@ class ToQTextDocument(Tokenizer): self._document.setUndoRedoEnabled(False) self._document.setDocumentMargin(0) - self._theme = TextDocumentTheme() self._styles: dict[int, T_TextStyle] = {} self._usedNotes: dict[str, int] = {} @@ -110,11 +94,6 @@ class ToQTextDocument(Tokenizer): # Setters ## - def setTheme(self, theme: TextDocumentTheme) -> None: - """Set the document colour theme.""" - self._theme = theme - return - def setPageLayout( self, width: float, height: float, top: float, bottom: float, left: float, right: float ) -> None: diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index c2a75479..5b296fa8 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -44,7 +44,8 @@ from novelwriter.error import logException from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.modified import NIconToolButton -from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument +from novelwriter.formats.shared import TextDocumentTheme +from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.types import ( QtAlignCenterTop, QtKeepAnchor, QtMouseLeft, QtMoveAnchor, diff --git a/novelwriter/types.py b/novelwriter/types.py index ab684dee..e9db0876 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -65,6 +65,10 @@ QtPaintAnitAlias = QPainter.RenderHint.Antialiasing QtMouseOver = QStyle.StateFlag.State_MouseOver QtSelected = QStyle.StateFlag.State_Selected +# Qt Colour Types + +QtHexRgb = QColor.NameFormat.HexRgb + # Qt Tree and Table Types QtDecoration = Qt.ItemDataRole.DecorationRole diff --git a/tests/reference/fmtToDocX_SaveDocument_styles.xml b/tests/reference/fmtToDocX_SaveDocument_styles.xml index a8c8a16f..0fb4e06b 100644 --- a/tests/reference/fmtToDocX_SaveDocument_styles.xml +++ b/tests/reference/fmtToDocX_SaveDocument_styles.xml @@ -50,7 +50,7 @@ - + @@ -65,7 +65,7 @@ - + @@ -80,7 +80,7 @@ - + @@ -95,7 +95,7 @@ - + diff --git a/tests/reference/fmtToOdt_SaveFlat_document.fodt b/tests/reference/fmtToOdt_SaveFlat_document.fodt index 14c9e9a4..6280be49 100644 --- a/tests/reference/fmtToOdt_SaveFlat_document.fodt +++ b/tests/reference/fmtToOdt_SaveFlat_document.fodt @@ -1,13 +1,13 @@ - 2024-10-17T22:22:08 + 2024-10-22T11:39:53 novelWriter/2.6a1 Jane Smith 1234 P42DT12H34M56S Test Project - 2024-10-17T22:22:08 + 2024-10-22T11:39:53 Jane Smith @@ -47,19 +47,19 @@ - + - + - + - + diff --git a/tests/reference/fmtToOdt_SaveFull_styles.xml b/tests/reference/fmtToOdt_SaveFull_styles.xml index 708be359..e66e2d7e 100644 --- a/tests/reference/fmtToOdt_SaveFull_styles.xml +++ b/tests/reference/fmtToOdt_SaveFull_styles.xml @@ -37,19 +37,19 @@ - + - + - + - + diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm index 794071e8..9f9802ae 100644 --- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm +++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm @@ -5,12 +5,12 @@ Lorem Ipsum