diff --git a/novelwriter/common.py b/novelwriter/common.py
index 64a4de1b..1dd2c64c 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -38,7 +38,7 @@ from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl
-from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo
+from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -434,6 +434,30 @@ def describeFont(font: QFont) -> str:
return "Error"
+def fontMatcher(font: QFont) -> QFont:
+ """Make sure the font is the correct family, if possible. This
+ ensures that Qt doesn't re-use another font under the hood. The
+ default Qt5 font matching algorithm doesn't handle well changing
+ application fonts at runtime.
+ """
+ info = QFontInfo(font)
+ if (famRequest := font.family()) != (famActual := info.family()):
+ logger.warning("Font mismatch: Requested '%s', but got '%s'", famRequest, famActual)
+ db = QFontDatabase()
+ if famRequest in db.families():
+ styleRequest, sizeRequest = font.styleName(), font.pointSize()
+ logger.info("Lookup: %s, %s, %d pt", famRequest, styleRequest, sizeRequest)
+ temp = db.font(famRequest, styleRequest, sizeRequest)
+ temp.setPointSize(sizeRequest) # Make sure it isn't changed
+ famFound, styleFound, sizeFound = temp.family(), temp.styleName(), temp.pointSize()
+ if famFound == famRequest:
+ logger.info("Found: %s, %s, %d pt", famFound, styleFound, sizeFound)
+ return temp
+ logger.warning("Could not find a font match in the font database")
+ logger.warning("If you just changed font, you may need to restart the application")
+ return font
+
+
def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
"""A replacement for Python lambdas that works for Qt slots."""
def wrapper(*a_: Any) -> None:
@@ -441,6 +465,12 @@ def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
return wrapper
+def encodeMimeHandles(mimeData: QMimeData, handles: list[str]) -> None:
+ """Encode handles into a mime data object."""
+ mimeData.setData(nwConst.MIME_HANDLE, b"|".join(h.encode() for h in handles))
+ return
+
+
def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
"""Decode and split a mime data object with handles."""
return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|")
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 45dc01d9..d1ae57b5 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -40,7 +40,10 @@ from PyQt5.QtCore import (
from PyQt5.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import QApplication
-from novelwriter.common import NWConfigParser, checkInt, checkPath, describeFont, formatTimeStamp
+from novelwriter.common import (
+ NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
+ formatTimeStamp
+)
from novelwriter.constants import nwFiles, nwUnicode
from novelwriter.error import formatException, logException
@@ -369,10 +372,11 @@ class Config:
def setGuiFont(self, value: QFont | str | None) -> None:
"""Update the GUI's font style from settings."""
if isinstance(value, QFont):
- self.guiFont = value
+ self.guiFont = fontMatcher(value)
elif value and isinstance(value, str):
- self.guiFont = QFont()
- self.guiFont.fromString(value)
+ font = QFont()
+ font.fromString(value)
+ self.guiFont = fontMatcher(font)
else:
font = QFont()
fontDB = QFontDatabase()
@@ -382,11 +386,9 @@ class Config:
font.setPointSize(10)
else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
- self.guiFont = font
+ self.guiFont = fontMatcher(font)
logger.debug("GUI font set to: %s", describeFont(font))
-
QApplication.setFont(self.guiFont)
-
return
def setTextFont(self, value: QFont | str | None) -> None:
@@ -394,10 +396,11 @@ class Config:
set to default font.
"""
if isinstance(value, QFont):
- self.textFont = value
+ self.textFont = fontMatcher(value)
elif value and isinstance(value, str):
- self.textFont = QFont()
- self.textFont.fromString(value)
+ font = QFont()
+ font.fromString(value)
+ self.textFont = fontMatcher(font)
else:
fontDB = QFontDatabase()
fontFam = fontDB.families()
@@ -411,8 +414,8 @@ class Config:
font.setPointSize(12)
else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
- self.textFont = font
- logger.debug("Text font set to: %s", describeFont(font))
+ self.textFont = fontMatcher(font)
+ logger.debug("Text font set to: %s", describeFont(self.textFont))
return
##
diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index 5cbdfc0a..a98da986 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -178,7 +178,7 @@ class NWBuildDocument:
makeObj = ToQTextDocument(self._project)
makeObj.disableAnchors()
filtered = self._setupBuild(makeObj)
- makeObj.initDocument()
+ makeObj.initDocument(pdf=True)
yield from self._iterBuild(makeObj, filtered)
makeObj.closeDocument()
@@ -218,7 +218,7 @@ class NWBuildDocument:
textFont = QFont(CONFIG.textFont)
textFont.fromString(self._build.getStr("format.textFont"))
- bldObj.setFont(textFont)
+ bldObj.setTextFont(textFont)
bldObj.setLanguage(self._project.data.language)
bldObj.setPartitionFormat(
diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py
index df4d33c1..f8784ac2 100644
--- a/novelwriter/core/itemmodel.py
+++ b/novelwriter/core/itemmodel.py
@@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
from PyQt5.QtGui import QFont, QIcon
-from novelwriter.common import decodeMimeHandles, minmax
+from novelwriter.common import decodeMimeHandles, encodeMimeHandles, minmax
from novelwriter.constants import nwConst
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass
@@ -367,11 +367,11 @@ class ProjectModel(QAbstractItemModel):
def mimeData(self, indices: list[QModelIndex]) -> QMimeData:
"""Encode mime data about a selection."""
handles = [
- i.internalPointer().item.itemHandle.encode()
+ i.internalPointer().item.itemHandle
for i in indices if i.isValid() and i.column() == 0
]
mime = QMimeData()
- mime.setData(nwConst.MIME_HANDLE, b"|".join(handles))
+ encodeMimeHandles(mime, handles)
return mime
def canDropMimeData(
diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py
index 9af17216..2b75130d 100644
--- a/novelwriter/formats/tokenizer.py
+++ b/novelwriter/formats/tokenizer.py
@@ -35,7 +35,7 @@ from PyQt5.QtCore import QLocale
from PyQt5.QtGui import QColor, QFont
from novelwriter import CONFIG
-from novelwriter.common import checkInt, numberToRoman
+from novelwriter.common import checkInt, fontMatcher, numberToRoman
from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
trConst
@@ -302,9 +302,9 @@ class Tokenizer(ABC):
self._sceneStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE
return
- def setFont(self, font: QFont) -> None:
+ def setTextFont(self, font: QFont) -> None:
"""Set the build font."""
- self._textFont = font
+ self._textFont = fontMatcher(font)
return
def setLineHeight(self, height: float) -> None:
diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py
index 37ba5943..dedec194 100644
--- a/novelwriter/formats/toodt.py
+++ b/novelwriter/formats/toodt.py
@@ -43,7 +43,7 @@ from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer
-from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
+from novelwriter.types import FONT_STYLE, QtHexRgb
logger = logging.getLogger(__name__)
@@ -220,20 +220,14 @@ class ToOdt(Tokenizer):
# Initialise Variables
# ====================
- intWeight = FONT_WEIGHTS.get(self._textFont.weight(), 400)
- fontWeight = str(intWeight)
- fontBold = str(min(intWeight + 300, 900))
-
lang, _, country = self._dLocale.name().partition("_")
self._dLanguage = lang or self._dLanguage
self._dCountry = country or self._dCountry
self._fontFamily = self._textFont.family()
self._fontSize = self._textFont.pointSize()
- self._fontWeight = FONT_WEIGHT_MAP.get(fontWeight, fontWeight)
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable"
- self._fontBold = FONT_WEIGHT_MAP.get(fontBold, fontBold)
self._headWeight = self._fontBold if self._boldHeads else None
self._fBlockIndent = self._emToCm(self._blockIndent)
diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py
index 1787413e..0c11168f 100644
--- a/novelwriter/formats/toqdoc.py
+++ b/novelwriter/formats/toqdoc.py
@@ -29,8 +29,8 @@ from pathlib import Path
from PyQt5.QtCore import QMarginsF, QSizeF
from PyQt5.QtGui import (
- QColor, QFont, QPageSize, QTextBlockFormat, QTextCharFormat, QTextCursor,
- QTextDocument
+ QColor, QFont, QFontDatabase, QPageSize, QTextBlockFormat, QTextCharFormat,
+ QTextCursor, QTextDocument, QTextFrameFormat
)
from PyQt5.QtPrintSupport import QPrinter
@@ -74,11 +74,16 @@ class ToQTextDocument(Tokenizer):
self._usedFields: list[tuple[int, str]] = []
self._init = False
- self._bold = QFont.Weight.Bold
- self._normal = QFont.Weight.Normal
self._newPage = False
self._anchors = True
+ self._hWeight = QFont.Weight.Bold
+ self._dWeight = QFont.Weight.Normal
+ self._dItalic = False
+ self._dStrike = False
+ self._dUnderline = False
+
+ self._dpi = 96
self._pageSize = QPageSize(QPageSize.PageSizeId.A4)
self._pageMargins = QMarginsF(20.0, 20.0, 20.0, 20.0)
@@ -119,21 +124,37 @@ class ToQTextDocument(Tokenizer):
# Class Methods
##
- def initDocument(self) -> None:
+ def initDocument(self, pdf: bool = False) -> None:
"""Initialise all computed values of the document."""
super().initDocument()
+ if pdf:
+ fontDB = QFontDatabase()
+ family = self._textFont.family()
+ style = self._textFont.styleName()
+ self._dpi = 1200 if fontDB.isScalable(family, style) else 72
+
self._document.setUndoRedoEnabled(False)
self._document.blockSignals(True)
self._document.clear()
self._document.setDefaultFont(self._textFont)
- fPt = self._textFont.pointSizeF()
- fPx = fPt*96.0/72.0 # 1 em in pixels
+ # Default Styles
+ self._dWeight = self._textFont.weight()
+ self._dItalic = self._textFont.italic()
+ self._dStrike = self._textFont.strikeOut()
+ self._dUnderline = self._textFont.underline()
+
+ # Header Weight
+ self._hWeight = QFont.Weight.Bold if self._boldHeads else self._dWeight
# Scaled Sizes
# ============
+ fPt = self._textFont.pointSizeF()
+ fPx = fPt*96.0/72.0 # 1 em in pixels
+ mPx = fPx * self._dpi/96.0
+
self._mHead = {
BlockTyp.TITLE: (fPx * self._marginTitle[0], fPx * self._marginTitle[1]),
BlockTyp.HEAD1: (fPx * self._marginHead1[0], fPx * self._marginHead1[1]),
@@ -155,8 +176,8 @@ class ToQTextDocument(Tokenizer):
self._mMeta = (fPx * self._marginMeta[0], fPx * self._marginMeta[1])
self._mSep = (fPx * self._marginSep[0], fPx * self._marginSep[1])
- self._mIndent = fPx * 2.0
- self._tIndent = fPx * self._firstWidth
+ self._mIndent = mPx * 2.0
+ self._tIndent = mPx * self._firstWidth
# Text Formats
# ============
@@ -248,10 +269,12 @@ class ToQTextDocument(Tokenizer):
def saveDocument(self, path: Path) -> None:
"""Save the document as a PDF file."""
m = self._pageMargins
+ logger.info("Writing PDF at %d DPI", self._dpi)
printer = QPrinter(QPrinter.PrinterMode.HighResolution)
printer.setDocName(self._project.data.name)
printer.setCreator(f"novelWriter/{__version__}")
+ printer.setResolution(self._dpi)
printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
printer.setPageSize(self._pageSize)
printer.setPageMargins(m.left(), m.top(), m.right(), m.bottom(), QPrinter.Unit.Millimeter)
@@ -318,21 +341,21 @@ class ToQTextDocument(Tokenizer):
# Construct next format
if fmt == TextFmt.B_B:
- cFmt.setFontWeight(self._bold)
+ cFmt.setFontWeight(QFont.Weight.Bold)
elif fmt == TextFmt.B_E:
- cFmt.setFontWeight(self._normal)
+ cFmt.setFontWeight(self._dWeight)
elif fmt == TextFmt.I_B:
cFmt.setFontItalic(True)
elif fmt == TextFmt.I_E:
- cFmt.setFontItalic(False)
+ cFmt.setFontItalic(self._dItalic)
elif fmt == TextFmt.D_B:
cFmt.setFontStrikeOut(True)
elif fmt == TextFmt.D_E:
- cFmt.setFontStrikeOut(False)
+ cFmt.setFontStrikeOut(self._dStrike)
elif fmt == TextFmt.U_B:
cFmt.setFontUnderline(True)
elif fmt == TextFmt.U_E:
- cFmt.setFontUnderline(False)
+ cFmt.setFontUnderline(self._dUnderline)
elif fmt == TextFmt.M_B:
cFmt.setBackground(self._theme.highlight)
elif fmt == TextFmt.M_E:
@@ -376,7 +399,7 @@ class ToQTextDocument(Tokenizer):
cFmt.setAnchorHref(data)
elif fmt == TextFmt.HRF_E:
cFmt.setForeground(primary or self._theme.text)
- cFmt.setFontUnderline(False)
+ cFmt.setFontUnderline(self._dUnderline)
cFmt.setAnchor(False)
cFmt.setAnchorHref("")
elif fmt == TextFmt.FNOTE:
@@ -409,24 +432,36 @@ class ToQTextDocument(Tokenizer):
def _insertNewPageMarker(self, cursor: QTextCursor) -> None:
"""Insert a new page marker."""
if self._newPage:
- cursor.insertHtml("
")
+ bgCol = QColor(self._theme.text)
+ bgCol.setAlphaF(0.1)
+ fgCol = QColor(self._theme.text)
+ fgCol.setAlphaF(0.8)
- hFmt = cursor.blockFormat()
- hFmt.setBottomMargin(0.0)
- hFmt.setLineHeight(75.0, QtPropLineHeight)
- cursor.setBlockFormat(hFmt)
+ fFmt = QTextFrameFormat()
+ fFmt.setBorderStyle(QTextFrameFormat.BorderStyle.BorderStyle_None)
+ fFmt.setBackground(bgCol)
+ fFmt.setTopMargin(self._mSep[0])
+ fFmt.setBottomMargin(self._mSep[1])
bFmt = QTextBlockFormat(self._blockFmt)
bFmt.setAlignment(QtAlignCenter)
bFmt.setTopMargin(0.0)
- bFmt.setLineHeight(75.0, QtPropLineHeight)
+ bFmt.setBottomMargin(0.0)
+ bFmt.setLineHeight(100.0, QtPropLineHeight)
cFmt = QTextCharFormat(self._charFmt)
+ cFmt.setFontItalic(False)
+ cFmt.setFontUnderline(False)
+ cFmt.setFontStrikeOut(False)
+ cFmt.setFontWeight(QFont.Weight.Normal)
cFmt.setFontPointSize(0.75*self._textFont.pointSizeF())
- cFmt.setForeground(self._theme.comment)
+ cFmt.setForeground(fgCol)
- newBlock(cursor, bFmt)
+ cursor.insertFrame(fFmt)
+ cursor.setBlockFormat(bFmt)
cursor.insertText(self._project.localLookup("New Page"), cFmt)
+ cursor.swap(self._document.rootFrame().lastCursorPosition())
+
return
def _genHeadStyle(self, hType: BlockTyp, hKey: str, rFmt: QTextBlockFormat) -> T_TextStyle:
@@ -440,7 +475,7 @@ class ToQTextDocument(Tokenizer):
hCol = self._colorHeads and hType != BlockTyp.TITLE
cFmt = QTextCharFormat(self._charFmt)
cFmt.setForeground(self._theme.head if hCol else self._theme.text)
- cFmt.setFontWeight(self._bold if self._boldHeads else self._normal)
+ cFmt.setFontWeight(self._hWeight)
cFmt.setFontPointSize(self._sHead.get(hType, 1.0))
if hKey and self._anchors:
cFmt.setAnchorNames([hKey])
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index a77869ca..1e53c7f7 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -53,7 +53,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.common import decodeMimeHandles, minmax, qtLambda, transferCase
+from novelwriter.common import decodeMimeHandles, fontMatcher, minmax, qtLambda, transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
from novelwriter.core.document import NWDocument
from novelwriter.enum import (
@@ -348,7 +348,9 @@ class GuiDocEditor(QPlainTextEdit):
SHARED.updateSpellCheckLanguage()
# Set the font. See issues #1862 and #1875.
- self.setFont(CONFIG.textFont)
+ font = fontMatcher(CONFIG.textFont)
+ self.setFont(font)
+ self._qDocument.setDefaultFont(font)
self.docHeader.updateFont()
self.docFooter.updateFont()
self.docSearch.updateFont()
@@ -1277,11 +1279,13 @@ class GuiDocEditor(QPlainTextEdit):
"""Process the word counter's finished signal."""
if self._docHandle and self._nwItem:
logger.debug("Updating word count")
+ needsRefresh = wCount != self._nwItem.wordCount
self._nwItem.setCharCount(cCount)
self._nwItem.setWordCount(wCount)
self._nwItem.setParaCount(pCount)
- self._nwItem.notifyToRefresh()
- self.docFooter.updateWordCount(wCount, False)
+ if needsRefresh:
+ self._nwItem.notifyToRefresh()
+ self.docFooter.updateWordCount(wCount, False)
return
@pyqtSlot()
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index 9e1b6f5f..d57303a6 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -219,7 +219,7 @@ class GuiDocViewer(QTextBrowser):
qDoc = ToQTextDocument(SHARED.project)
qDoc.setJustify(CONFIG.doJustify)
qDoc.setDialogHighlight(True)
- qDoc.setFont(CONFIG.textFont)
+ qDoc.setTextFont(CONFIG.textFont)
qDoc.setTheme(self._docTheme)
qDoc.initDocument()
qDoc.setKeywords(True)
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 8d83bd92..d0f74bdc 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -1051,11 +1051,6 @@ class GuiMain(QMainWindow):
self.initMain()
self.saveDocument()
- if restart:
- SHARED.info(self.tr(
- "Some changes will not be applied until novelWriter has been restarted."
- ))
-
if tree:
SHARED.project.tree.refreshAllItems()
@@ -1088,6 +1083,11 @@ class GuiMain(QMainWindow):
self._lastTotalCount = 0
self._updateStatusWordCount()
+ if restart:
+ SHARED.info(self.tr(
+ "Some changes will not be applied until novelWriter has been restarted."
+ ))
+
return
@pyqtSlot()
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index e5221cd2..dc64a316 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.common import describeFont, qtLambda
+from novelwriter.common import describeFont, fontMatcher, qtLambda
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.configlayout import (
@@ -1281,8 +1281,9 @@ class _FormattingTab(NScrollableForm):
# Text Format
# ===========
- self._textFont = QFont()
- self._textFont.fromString(self._build.getStr("format.textFont"))
+ font = QFont()
+ font.fromString(self._build.getStr("format.textFont"))
+ self._textFont = fontMatcher(font)
self.textFont.setText(describeFont(self._textFont))
self.textFont.setCursorPosition(0)
@@ -1437,9 +1438,9 @@ class _FormattingTab(NScrollableForm):
"""Open the QFontDialog and set a font for the font style."""
font, status = SHARED.getFont(self._textFont, CONFIG.nativeFont)
if status:
- self.textFont.setText(describeFont(font))
+ self._textFont = fontMatcher(font)
+ self.textFont.setText(describeFont(self._textFont))
self.textFont.setCursorPosition(0)
- self._textFont = font
return
@pyqtSlot(int)
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index 8eae1ba0..ead951c8 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -27,18 +27,19 @@ from xml.etree import ElementTree as ET
import pytest
-from PyQt5.QtCore import QUrl
-from PyQt5.QtGui import QColor, QDesktopServices, QFontDatabase
+from PyQt5.QtCore import QMimeData, QUrl
+from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.common import (
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
- checkString, checkStringNone, checkUuid, compact, cssCol, describeFont,
- elide, firstFloat, formatFileFilter, formatInt, formatTime,
- formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle,
- isItemClass, isItemLayout, isItemType, isListInstance, isTitleTag,
- jsonEncode, makeFileNameSafe, minmax, numberToRoman, openExternalPath,
- readTextFile, simplified, transferCase, uniqueCompact, xmlElement,
- xmlIndent, xmlSubElem, yesNo
+ checkString, checkStringNone, checkUuid, compact, cssCol,
+ decodeMimeHandles, describeFont, elide, encodeMimeHandles, firstFloat,
+ fontMatcher, formatFileFilter, formatInt, formatTime, formatTimeStamp,
+ formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
+ isItemLayout, isItemType, isListInstance, isTitleTag, jsonEncode,
+ makeFileNameSafe, minmax, numberToRoman, openExternalPath, readTextFile,
+ simplified, transferCase, uniqueCompact, xmlElement, xmlIndent, xmlSubElem,
+ yesNo
)
from tests.mocked import causeOSError
@@ -528,6 +529,34 @@ def testBaseCommon_describeFont():
assert describeFont(None) == "Error" # type: ignore
+@pytest.mark.base
+def testBaseCommon_fontMatcher(monkeypatch):
+ """Test the fontMatcher function."""
+ # Nonsense font is just returned
+ nonsense = QFont("nonesense", 10)
+ assert fontMatcher(nonsense) is nonsense
+
+ # General font
+ fontDB = QFontDatabase()
+ if len(fontDB.families()) > 1:
+ fontOne = QFont(fontDB.families()[0])
+ fontTwo = QFont(fontDB.families()[1])
+ check = QFont(fontOne)
+ check.setFamily(fontTwo.family())
+ with monkeypatch.context() as mp:
+ mp.setattr(QFontInfo, "family", lambda *a: "nonesense")
+ assert fontMatcher(check).family() == fontTwo.family()
+
+
+@pytest.mark.base
+def testBaseCommon_encodeDecodeMimeHandles(monkeypatch):
+ """Test the encodeMimeHandles and decodeMimeHandles functions."""
+ handles = ["0123456789abc", "123456789abcd", "23456789abcde"]
+ mimeData = QMimeData()
+ encodeMimeHandles(mimeData, handles)
+ assert decodeMimeHandles(mimeData) == handles
+
+
@pytest.mark.base
def testBaseCommon_jsonEncode():
"""Test the jsonEncode function."""
diff --git a/tests/test_base/test_base_error.py b/tests/test_base/test_base_error.py
index 05aff792..266243f6 100644
--- a/tests/test_base/test_base_error.py
+++ b/tests/test_base/test_base_error.py
@@ -101,7 +101,7 @@ def testBaseError_Handler(qtbot, monkeypatch, nwGUI):
with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.QApplication.exit", lambda *a: None)
- mp.setattr(nwGUI, "closeMain", causeException)
+ mp.setattr("novelwriter.guimain.GuiMain.closeMain", causeException)
exceptionHandler(Exception, "Error Message", None) # type: ignore
nwGUI.closeMain()
diff --git a/tests/test_formats/test_fmt_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py
index bab57bfc..cbfdb0ae 100644
--- a/tests/test_formats/test_fmt_tokenizer.py
+++ b/tests/test_formats/test_fmt_tokenizer.py
@@ -110,7 +110,7 @@ def testFmtToken_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(QFont("Monospace", 10))
+ tokens.setTextFont(QFont("Monospace", 10))
tokens.setLineHeight(2.0)
tokens.setBlockIndent(6.0)
tokens.setJustify(True)
diff --git a/tests/test_formats/test_fmt_toqdoc.py b/tests/test_formats/test_fmt_toqdoc.py
index 74387653..e1b361fc 100644
--- a/tests/test_formats/test_fmt_toqdoc.py
+++ b/tests/test_formats/test_fmt_toqdoc.py
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest
-from PyQt5.QtGui import QTextBlock, QTextCharFormat, QTextCursor
+from PyQt5.QtGui import QFont, QTextBlock, QTextCharFormat, QTextCursor
from novelwriter import CONFIG
from novelwriter.constants import nwUnicode
@@ -71,7 +71,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.TITLE][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.TITLE][1]
cFmt = charFmtInBlock(block, 1)
- assert cFmt.fontWeight() == doc._bold
+ assert cFmt.fontWeight() == QFont.Weight.Bold
assert cFmt.fontPointSize() == doc._sHead[BlockTyp.TITLE]
assert cFmt.foreground().color() == THEME.text
@@ -82,7 +82,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD1][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD1][1]
cFmt = charFmtInBlock(block, 1)
- assert cFmt.fontWeight() == doc._bold
+ assert cFmt.fontWeight() == QFont.Weight.Bold
assert cFmt.fontPointSize() == doc._sHead[BlockTyp.HEAD1]
assert cFmt.foreground().color() == THEME.head
@@ -93,7 +93,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD2][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD2][1]
cFmt = charFmtInBlock(block, 1)
- assert cFmt.fontWeight() == doc._bold
+ assert cFmt.fontWeight() == QFont.Weight.Bold
assert cFmt.fontPointSize() == doc._sHead[BlockTyp.HEAD2]
assert cFmt.foreground().color() == THEME.head
@@ -104,7 +104,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD3][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD3][1]
cFmt = charFmtInBlock(block, 1)
- assert cFmt.fontWeight() == doc._bold
+ assert cFmt.fontWeight() == QFont.Weight.Bold
assert cFmt.fontPointSize() == doc._sHead[BlockTyp.HEAD3]
assert cFmt.foreground().color() == THEME.head
@@ -115,7 +115,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD4][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD4][1]
cFmt = charFmtInBlock(block, 1)
- assert cFmt.fontWeight() == doc._bold
+ assert cFmt.fontWeight() == QFont.Weight.Bold
assert cFmt.fontPointSize() == doc._sHead[BlockTyp.HEAD4]
assert cFmt.foreground().color() == THEME.head
@@ -478,11 +478,11 @@ def testFmtToQTextDocument_TextCharFormats(mockGUI):
block = doc.document.findBlockByNumber(1)
assert block.text() == "With bold text"
cFmt = charFmtInBlock(block, 1)
- assert cFmt.fontWeight() == doc._normal
+ assert cFmt.fontWeight() == doc._dWeight
cFmt = charFmtInBlock(block, 6)
- assert cFmt.fontWeight() == doc._bold
+ assert cFmt.fontWeight() == QFont.Weight.Bold
cFmt = charFmtInBlock(block, 10)
- assert cFmt.fontWeight() == doc._normal
+ assert cFmt.fontWeight() == doc._dWeight
# 2: Italic
block = doc.document.findBlockByNumber(2)
diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py
index 6d94477a..7aa744c2 100644
--- a/tests/test_tools/test_tools_manuscript.py
+++ b/tests/test_tools/test_tools_manuscript.py
@@ -48,7 +48,7 @@ def testToolManuscript_Init(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
buildTestProject(nwGUI, projPath)
nwGUI.openProject(projPath)
SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi")
- allText = "New Novel\nBy Jane Doe\n\nNew Page\nA Chapter\n\t\tHi"
+ allText = "New Novel\nBy Jane Doe\nNew Page\n\nA Chapter\n\t\tHi"
nwGUI.mainMenu.aBuildManuscript.activate(QAction.ActionEvent.Trigger)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiManuscript) is not None, timeout=1000)