From 7e1f1a725cb5786c19caf5bbbd2ff62b65e5303e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 May 2024 16:57:50 +0200 Subject: [PATCH] Switch Tokenizer to use QFont instead of font family and size --- novelwriter/core/buildsettings.py | 6 ++-- novelwriter/core/docbuild.py | 13 +++------ novelwriter/core/tokenizer.py | 12 ++++---- novelwriter/dialogs/preferences.py | 12 ++++---- novelwriter/tools/manuscript.py | 24 +++++++--------- novelwriter/tools/manussettings.py | 40 ++++++++++---------------- novelwriter/types.py | 35 +++++++++++++++++++++- tests/test_core/test_core_tokenizer.py | 12 ++++---- 8 files changed, 81 insertions(+), 73 deletions(-) diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index 932dc0f7..aa295b6b 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -76,8 +76,7 @@ SETTINGS_TEMPLATE = { "text.includeBodyText": (bool, True), "text.ignoredKeywords": (str, ""), "text.addNoteHeadings": (bool, True), - "format.textFont": (str, CONFIG.textFont.family()), - "format.textSize": (int, 12), + "format.textFont": (str, CONFIG.textFont.toString()), "format.lineHeight": (float, 1.15, 0.75, 3.0), "format.justifyText": (bool, False), "format.stripUnicode": (bool, False), @@ -125,8 +124,7 @@ SETTINGS_LABELS = { "text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"), "format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"), - "format.textFont": QT_TRANSLATE_NOOP("Builds", "Font Family"), - "format.textSize": QT_TRANSLATE_NOOP("Builds", "Font Size"), + "format.textFont": QT_TRANSLATE_NOOP("Builds", "Text Font"), "format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"), "format.grpOptions": QT_TRANSLATE_NOOP("Builds", "Text Options"), "format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"), diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index a2a2ba87..1b5350a8 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -28,7 +28,7 @@ import logging from collections.abc import Iterable from pathlib import Path -from PyQt5.QtGui import QFont, QFontInfo +from PyQt5.QtGui import QFont from novelwriter import CONFIG from novelwriter.constants import nwLabels @@ -279,13 +279,9 @@ class NWBuildDocument: def _setupBuild(self, bldObj: Tokenizer) -> dict: """Configure the build object.""" # Get Settings - textFont = self._build.getStr("format.textFont") - textSize = self._build.getInt("format.textSize") - - fontFamily = textFont or CONFIG.textFont.family() - bldFont = QFont(fontFamily, textSize) - fontInfo = QFontInfo(bldFont) - textFixed = fontInfo.fixedPitch() + textFont = QFont(CONFIG.textFont) + textFont.fromString(self._build.getStr("format.textFont")) + bldObj.setFont(textFont) bldObj.setTitleFormat( self._build.getStr("headings.fmtTitle"), @@ -324,7 +320,6 @@ class NWBuildDocument: self._build.getBool("headings.breakScene") ) - bldObj.setFont(fontFamily, textSize, textFixed) bldObj.setJustify(self._build.getBool("format.justifyText")) bldObj.setLineHeight(self._build.getFloat("format.lineHeight")) bldObj.setKeepLineBreaks(self._build.getBool("format.keepBreaks")) diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 448cb2ed..5eefc616 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -34,7 +34,9 @@ from pathlib import Path from time import time from PyQt5.QtCore import QCoreApplication, QRegularExpression +from PyQt5.QtGui import QFont +from novelwriter import CONFIG from novelwriter.common import checkInt, formatTimeStamp, numberToRoman from novelwriter.constants import ( nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst @@ -139,9 +141,7 @@ class Tokenizer(ABC): self._markdown: list[str] = [] # User Settings - self._textFont = "Serif" # Output text font - self._textSize = 11 # Output text size - self._textFixed = False # Fixed width text + self._textFont = CONFIG.textFont # Output text font self._lineHeight = 1.15 # Line height in units of em self._blockIndent = 4.00 # Block indent in units of em self._firstIndent = False # Enable first line indent @@ -315,11 +315,9 @@ class Tokenizer(ABC): ) return - def setFont(self, family: str, size: int, isFixed: bool = False) -> None: + def setFont(self, font: QFont) -> None: """Set the build font.""" - self._textFont = family - self._textSize = round(int(size)) - self._textFixed = isFixed + self._textFont = font return def setLineHeight(self, height: float) -> None: diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 3158fa24..affe3117 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -183,7 +183,7 @@ class GuiPreferences(QDialog): self.guiFont.setMinimumWidth(fontWidth) self.guiFont.setText(describeFont(self._guiFont)) self.guiFont.setCursorPosition(0) - self.guiFontButton = NIconToolButton(self, iSz, "more") + self.guiFontButton = NIconToolButton(self, iSz, "font") self.guiFontButton.clicked.connect(self._selectGuiFont) self.mainForm.addRow( self.tr("Application font"), self.guiFont, @@ -233,7 +233,7 @@ class GuiPreferences(QDialog): self.textFont.setMinimumWidth(fontWidth) self.textFont.setText(describeFont(CONFIG.textFont)) self.textFont.setCursorPosition(0) - self.textFontButton = NIconToolButton(self, iSz, "more") + self.textFontButton = NIconToolButton(self, iSz, "font") self.textFontButton.clicked.connect(self._selectTextFont) self.mainForm.addRow( self.tr("Document font"), self.textFont, @@ -695,7 +695,7 @@ class GuiPreferences(QDialog): self.quoteSym["SO"].setFixedWidth(boxFixed) self.quoteSym["SO"].setAlignment(QtAlignCenter) self.quoteSym["SO"].setText(CONFIG.fmtSQuoteOpen) - self.btnSingleStyleO = NIconToolButton(self, iSz, "more") + self.btnSingleStyleO = NIconToolButton(self, iSz, "quote") self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO")) self.mainForm.addRow( self.tr("Single quote open style"), self.quoteSym["SO"], @@ -709,7 +709,7 @@ class GuiPreferences(QDialog): self.quoteSym["SC"].setFixedWidth(boxFixed) self.quoteSym["SC"].setAlignment(QtAlignCenter) self.quoteSym["SC"].setText(CONFIG.fmtSQuoteClose) - self.btnSingleStyleC = NIconToolButton(self, iSz, "more") + self.btnSingleStyleC = NIconToolButton(self, iSz, "quote") self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC")) self.mainForm.addRow( self.tr("Single quote close style"), self.quoteSym["SC"], @@ -724,7 +724,7 @@ class GuiPreferences(QDialog): self.quoteSym["DO"].setFixedWidth(boxFixed) self.quoteSym["DO"].setAlignment(QtAlignCenter) self.quoteSym["DO"].setText(CONFIG.fmtDQuoteOpen) - self.btnDoubleStyleO = NIconToolButton(self, iSz, "more") + self.btnDoubleStyleO = NIconToolButton(self, iSz, "quote") self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO")) self.mainForm.addRow( self.tr("Double quote open style"), self.quoteSym["DO"], @@ -738,7 +738,7 @@ class GuiPreferences(QDialog): self.quoteSym["DC"].setFixedWidth(boxFixed) self.quoteSym["DC"].setAlignment(QtAlignCenter) self.quoteSym["DC"].setText(CONFIG.fmtDQuoteClose) - self.btnDoubleStyleC = NIconToolButton(self, iSz, "more") + self.btnDoubleStyleC = NIconToolButton(self, iSz, "quote") self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC")) self.mainForm.addRow( self.tr("Double quote close style"), self.quoteSym["DC"], diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index ca19e6d0..d70442f0 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -31,7 +31,7 @@ from time import time from typing import TYPE_CHECKING from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot -from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QPalette, QResizeEvent +from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt5.QtWidgets import ( QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout, @@ -404,10 +404,10 @@ class GuiManuscript(NToolDialog): """Update the preview widget and set relevant values.""" self.docPreview.setContent(data) self.docPreview.setBuildName(build.name) - self.docPreview.setTextFont( - build.getStr("format.textFont"), - build.getInt("format.textSize") - ) + + textFont = QFont() + textFont.fromString(build.getStr("format.textFont")) + self.docPreview.setTextFont(textFont) self.docPreview.setJustify( build.getBool("format.justifyText") ) @@ -787,7 +787,7 @@ class _PreviewWidget(QTextBrowser): self._updateDocMargins() self._updateBuildAge() - self.setTextFont(CONFIG.textFont.family(), CONFIG.textFont.pointSize()) + self.setTextFont(CONFIG.textFont) # Age Timer self.ageTimer = QTimer(self) @@ -817,18 +817,14 @@ class _PreviewWidget(QTextBrowser): self.document().setDefaultTextOption(pOptions) return - def setTextFont(self, family: str, size: int) -> None: + def setTextFont(self, font: QFont) -> None: """Set the text font properties and then reset for sub-widgets. This needs special attention since there appears to be a bug in Qt 5.15.3. See issues #1862 and #1875. """ - if family and size > 4: - font = self.font() - font.setFamily(family) - font.setPointSize(size) - self.setFont(font) - self.buildProgress.setFont(SHARED.theme.guiFont) - self.ageLabel.setFont(SHARED.theme.guiFontSmall) + self.setFont(font) + self.buildProgress.setFont(SHARED.theme.guiFont) + self.ageLabel.setFont(SHARED.theme.guiFontSmall) return ## diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index e49bb546..53a84be6 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -37,6 +37,7 @@ from PyQt5.QtWidgets import ( ) from novelwriter import CONFIG, SHARED +from novelwriter.common import describeFont from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, trConst from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.extensions.configlayout import ( @@ -1052,6 +1053,7 @@ class _FormatTab(NScrollableForm): self._build = build self._unitScale = 1.0 + self._textFont = QFont(CONFIG.textFont) iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize @@ -1063,24 +1065,16 @@ class _FormatTab(NScrollableForm): self.addGroupLabel(self._build.getLabel("format.grpFormat")) - # Font Family + # Text Font self.textFont = QLineEdit(self) self.textFont.setReadOnly(True) - self.btnTextFont = NIconToolButton(self, iSz, "more") + self.btnTextFont = NIconToolButton(self, iSz, "font") self.btnTextFont.clicked.connect(self._selectFont) self.addRow( self._build.getLabel("format.textFont"), self.textFont, - button=self.btnTextFont, stretch=(3, 2) + button=self.btnTextFont, stretch=(1, 1) ) - # Font Size - self.textSize = NSpinBox(self) - self.textSize.setMinimum(8) - self.textSize.setMaximum(60) - self.textSize.setSingleStep(1) - self.textSize.setMinimumWidth(spW) - self.addRow(self._build.getLabel("format.textSize"), self.textSize, unit="pt") - # Line Height self.lineHeight = NDoubleSpinBox(self) self.lineHeight.setFixedWidth(spW) @@ -1175,14 +1169,13 @@ class _FormatTab(NScrollableForm): def loadContent(self) -> None: """Populate the widgets.""" - textFont = self._build.getStr("format.textFont") - if not textFont: - textFont = str(CONFIG.textFont.family()) + self._textFont = QFont() + self._textFont.fromString(self._build.getStr("format.textFont")) + + self.textFont.setText(describeFont(self._textFont)) + self.textFont.setCursorPosition(0) - self.textFont.setText(textFont) - self.textSize.setValue(self._build.getInt("format.textSize")) self.lineHeight.setValue(self._build.getFloat("format.lineHeight")) - self.justifyText.setChecked(self._build.getBool("format.justifyText")) self.stripUnicode.setChecked(self._build.getBool("format.stripUnicode")) self.replaceTabs.setChecked(self._build.getBool("format.replaceTabs")) @@ -1219,8 +1212,7 @@ class _FormatTab(NScrollableForm): def saveContent(self) -> None: """Save choices back into build object.""" - self._build.setValue("format.textFont", self.textFont.text()) - self._build.setValue("format.textSize", self.textSize.value()) + self._build.setValue("format.textFont", self._textFont.toString()) self._build.setValue("format.lineHeight", self.lineHeight.value()) self._build.setValue("format.justifyText", self.justifyText.isChecked()) @@ -1249,13 +1241,11 @@ class _FormatTab(NScrollableForm): @pyqtSlot() def _selectFont(self) -> None: """Open the QFontDialog and set a font for the font style.""" - currFont = QFont() - currFont.setFamily(self.textFont.text()) - currFont.setPointSize(self.textSize.value()) - newFont, status = QFontDialog.getFont(currFont, self) + font, status = QFontDialog.getFont(self._textFont, self) if status: - self.textFont.setText(newFont.family()) - self.textSize.setValue(newFont.pointSize()) + self.textFont.setText(describeFont(font)) + self.textFont.setCursorPosition(0) + self._textFont = font return @pyqtSlot(int) diff --git a/novelwriter/types.py b/novelwriter/types.py index e4051f8e..d0e8eff9 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -24,7 +24,7 @@ along with this program. If not, see . from __future__ import annotations from PyQt5.QtCore import QRegularExpression, Qt -from PyQt5.QtGui import QColor, QPainter, QTextCursor, QTextFormat +from PyQt5.QtGui import QColor, QFont, QPainter, QTextCursor, QTextFormat from PyQt5.QtWidgets import QDialogButtonBox, QSizePolicy, QStyle # Qt Alignment Flags @@ -105,3 +105,36 @@ QtSizeMinimumExpanding = QSizePolicy.Policy.MinimumExpanding # Other QRegExUnicode = QRegularExpression.PatternOption.UseUnicodePropertiesOption + +# Maps + +FONT_WEIGHTS: dict[int, int] = { + QFont.Weight.Thin: 100, + QFont.Weight.ExtraLight: 200, + QFont.Weight.Light: 300, + QFont.Weight.Normal: 400, + QFont.Weight.Medium: 500, + QFont.Weight.DemiBold: 600, + QFont.Weight.Bold: 700, + QFont.Weight.ExtraBold: 800, + QFont.Weight.Black: 900, +} + +FONT_STRETCH: dict[int, str] = { + QFont.AnyStretch: "normal", + QFont.UltraCondensed: "ultra-condensed", + QFont.ExtraCondensed: "extra-condensed", + QFont.Condensed: "condensed", + QFont.SemiCondensed: "semi-condensed", + QFont.Unstretched: "normal", + QFont.SemiExpanded: "semi-expanded", + QFont.Expanded: "expanded", + QFont.ExtraExpanded: "extra-expanded", + QFont.UltraExpanded: "ultra-expanded", +} + +FONT_STYLE: dict[int, str] = { + QFont.Style.StyleNormal: "normal", + QFont.Style.StyleItalic: "italic", + QFont.Style.StyleOblique: "oblique", +} diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 5e45916e..39e37b89 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -24,6 +24,8 @@ import json import pytest +from PyQt5.QtGui import QFont + from novelwriter.constants import nwHeadFmt from novelwriter.core.project import NWProject from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape @@ -50,9 +52,7 @@ def testCoreToken_Setters(mockGUI): assert tokens._fmtScene == nwHeadFmt.TITLE assert tokens._fmtHScene == nwHeadFmt.TITLE assert tokens._fmtSection == nwHeadFmt.TITLE - assert tokens._textFont == "Serif" - assert tokens._textSize == 11 - assert tokens._textFixed is False + assert tokens._textFont == QFont("Serif", 11) assert tokens._lineHeight == 1.15 assert tokens._blockIndent == 4.0 assert tokens._doJustify is False @@ -82,7 +82,7 @@ def testCoreToken_Setters(mockGUI): tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True) tokens.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", True) tokens.setSectionFormat(f"X: {nwHeadFmt.TITLE}", True) - tokens.setFont("Monospace", 10, True) + tokens.setFont(QFont("Monospace", 10)) tokens.setLineHeight(2.0) tokens.setBlockIndent(6.0) tokens.setJustify(True) @@ -106,9 +106,7 @@ def testCoreToken_Setters(mockGUI): assert tokens._fmtScene == f"S: {nwHeadFmt.TITLE}" assert tokens._fmtHScene == f"H: {nwHeadFmt.TITLE}" assert tokens._fmtSection == f"X: {nwHeadFmt.TITLE}" - assert tokens._textFont == "Monospace" - assert tokens._textSize == 10 - assert tokens._textFixed is True + assert tokens._textFont == QFont("Monospace", 10) assert tokens._lineHeight == 2.0 assert tokens._blockIndent == 6.0 assert tokens._doJustify is True