Fix font style and PDF issues (#2122)

This commit is contained in:
Veronica Berglyd Olsen
2024-11-27 22:59:20 +01:00
committed by GitHub
16 changed files with 184 additions and 88 deletions
+31 -1
View File
@@ -38,7 +38,7 @@ from urllib.parse import urljoin
from urllib.request import pathname2url from urllib.request import pathname2url
from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl 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.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -434,6 +434,30 @@ def describeFont(font: QFont) -> str:
return "Error" 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: def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
"""A replacement for Python lambdas that works for Qt slots.""" """A replacement for Python lambdas that works for Qt slots."""
def wrapper(*a_: Any) -> None: def wrapper(*a_: Any) -> None:
@@ -441,6 +465,12 @@ def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
return wrapper 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]: def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
"""Decode and split a mime data object with handles.""" """Decode and split a mime data object with handles."""
return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|") return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|")
+15 -12
View File
@@ -40,7 +40,10 @@ from PyQt5.QtCore import (
from PyQt5.QtGui import QFont, QFontDatabase from PyQt5.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import QApplication 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.constants import nwFiles, nwUnicode
from novelwriter.error import formatException, logException from novelwriter.error import formatException, logException
@@ -369,10 +372,11 @@ class Config:
def setGuiFont(self, value: QFont | str | None) -> None: def setGuiFont(self, value: QFont | str | None) -> None:
"""Update the GUI's font style from settings.""" """Update the GUI's font style from settings."""
if isinstance(value, QFont): if isinstance(value, QFont):
self.guiFont = value self.guiFont = fontMatcher(value)
elif value and isinstance(value, str): elif value and isinstance(value, str):
self.guiFont = QFont() font = QFont()
self.guiFont.fromString(value) font.fromString(value)
self.guiFont = fontMatcher(font)
else: else:
font = QFont() font = QFont()
fontDB = QFontDatabase() fontDB = QFontDatabase()
@@ -382,11 +386,9 @@ class Config:
font.setPointSize(10) font.setPointSize(10)
else: else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = font self.guiFont = fontMatcher(font)
logger.debug("GUI font set to: %s", describeFont(font)) logger.debug("GUI font set to: %s", describeFont(font))
QApplication.setFont(self.guiFont) QApplication.setFont(self.guiFont)
return return
def setTextFont(self, value: QFont | str | None) -> None: def setTextFont(self, value: QFont | str | None) -> None:
@@ -394,10 +396,11 @@ class Config:
set to default font. set to default font.
""" """
if isinstance(value, QFont): if isinstance(value, QFont):
self.textFont = value self.textFont = fontMatcher(value)
elif value and isinstance(value, str): elif value and isinstance(value, str):
self.textFont = QFont() font = QFont()
self.textFont.fromString(value) font.fromString(value)
self.textFont = fontMatcher(font)
else: else:
fontDB = QFontDatabase() fontDB = QFontDatabase()
fontFam = fontDB.families() fontFam = fontDB.families()
@@ -411,8 +414,8 @@ class Config:
font.setPointSize(12) font.setPointSize(12)
else: else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont) font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.textFont = font self.textFont = fontMatcher(font)
logger.debug("Text font set to: %s", describeFont(font)) logger.debug("Text font set to: %s", describeFont(self.textFont))
return return
## ##
+2 -2
View File
@@ -178,7 +178,7 @@ class NWBuildDocument:
makeObj = ToQTextDocument(self._project) makeObj = ToQTextDocument(self._project)
makeObj.disableAnchors() makeObj.disableAnchors()
filtered = self._setupBuild(makeObj) filtered = self._setupBuild(makeObj)
makeObj.initDocument() makeObj.initDocument(pdf=True)
yield from self._iterBuild(makeObj, filtered) yield from self._iterBuild(makeObj, filtered)
makeObj.closeDocument() makeObj.closeDocument()
@@ -218,7 +218,7 @@ class NWBuildDocument:
textFont = QFont(CONFIG.textFont) textFont = QFont(CONFIG.textFont)
textFont.fromString(self._build.getStr("format.textFont")) textFont.fromString(self._build.getStr("format.textFont"))
bldObj.setFont(textFont) bldObj.setTextFont(textFont)
bldObj.setLanguage(self._project.data.language) bldObj.setLanguage(self._project.data.language)
bldObj.setPartitionFormat( bldObj.setPartitionFormat(
+3 -3
View File
@@ -31,7 +31,7 @@ from typing import TYPE_CHECKING
from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
from PyQt5.QtGui import QFont, QIcon 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.constants import nwConst
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass from novelwriter.enum import nwItemClass
@@ -367,11 +367,11 @@ class ProjectModel(QAbstractItemModel):
def mimeData(self, indices: list[QModelIndex]) -> QMimeData: def mimeData(self, indices: list[QModelIndex]) -> QMimeData:
"""Encode mime data about a selection.""" """Encode mime data about a selection."""
handles = [ handles = [
i.internalPointer().item.itemHandle.encode() i.internalPointer().item.itemHandle
for i in indices if i.isValid() and i.column() == 0 for i in indices if i.isValid() and i.column() == 0
] ]
mime = QMimeData() mime = QMimeData()
mime.setData(nwConst.MIME_HANDLE, b"|".join(handles)) encodeMimeHandles(mime, handles)
return mime return mime
def canDropMimeData( def canDropMimeData(
+3 -3
View File
@@ -35,7 +35,7 @@ from PyQt5.QtCore import QLocale
from PyQt5.QtGui import QColor, QFont from PyQt5.QtGui import QColor, QFont
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.common import checkInt, numberToRoman from novelwriter.common import checkInt, fontMatcher, numberToRoman
from novelwriter.constants import ( from novelwriter.constants import (
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode, nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
trConst trConst
@@ -302,9 +302,9 @@ class Tokenizer(ABC):
self._sceneStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE self._sceneStyle |= BlockFmt.PBB if pageBreak else BlockFmt.NONE
return return
def setFont(self, font: QFont) -> None: def setTextFont(self, font: QFont) -> None:
"""Set the build font.""" """Set the build font."""
self._textFont = font self._textFont = fontMatcher(font)
return return
def setLineHeight(self, height: float) -> None: def setLineHeight(self, height: float) -> None:
+1 -7
View File
@@ -43,7 +43,7 @@ from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer 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__) logger = logging.getLogger(__name__)
@@ -220,20 +220,14 @@ class ToOdt(Tokenizer):
# Initialise Variables # 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("_") lang, _, country = self._dLocale.name().partition("_")
self._dLanguage = lang or self._dLanguage self._dLanguage = lang or self._dLanguage
self._dCountry = country or self._dCountry self._dCountry = country or self._dCountry
self._fontFamily = self._textFont.family() self._fontFamily = self._textFont.family()
self._fontSize = self._textFont.pointSize() self._fontSize = self._textFont.pointSize()
self._fontWeight = FONT_WEIGHT_MAP.get(fontWeight, fontWeight)
self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal") self._fontStyle = FONT_STYLE.get(self._textFont.style(), "normal")
self._fontPitch = "fixed" if self._textFont.fixedPitch() else "variable" 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._headWeight = self._fontBold if self._boldHeads else None
self._fBlockIndent = self._emToCm(self._blockIndent) self._fBlockIndent = self._emToCm(self._blockIndent)
+59 -24
View File
@@ -29,8 +29,8 @@ from pathlib import Path
from PyQt5.QtCore import QMarginsF, QSizeF from PyQt5.QtCore import QMarginsF, QSizeF
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QFont, QPageSize, QTextBlockFormat, QTextCharFormat, QTextCursor, QColor, QFont, QFontDatabase, QPageSize, QTextBlockFormat, QTextCharFormat,
QTextDocument QTextCursor, QTextDocument, QTextFrameFormat
) )
from PyQt5.QtPrintSupport import QPrinter from PyQt5.QtPrintSupport import QPrinter
@@ -74,11 +74,16 @@ class ToQTextDocument(Tokenizer):
self._usedFields: list[tuple[int, str]] = [] self._usedFields: list[tuple[int, str]] = []
self._init = False self._init = False
self._bold = QFont.Weight.Bold
self._normal = QFont.Weight.Normal
self._newPage = False self._newPage = False
self._anchors = True 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._pageSize = QPageSize(QPageSize.PageSizeId.A4)
self._pageMargins = QMarginsF(20.0, 20.0, 20.0, 20.0) self._pageMargins = QMarginsF(20.0, 20.0, 20.0, 20.0)
@@ -119,21 +124,37 @@ class ToQTextDocument(Tokenizer):
# Class Methods # Class Methods
## ##
def initDocument(self) -> None: def initDocument(self, pdf: bool = False) -> None:
"""Initialise all computed values of the document.""" """Initialise all computed values of the document."""
super().initDocument() 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.setUndoRedoEnabled(False)
self._document.blockSignals(True) self._document.blockSignals(True)
self._document.clear() self._document.clear()
self._document.setDefaultFont(self._textFont) self._document.setDefaultFont(self._textFont)
fPt = self._textFont.pointSizeF() # Default Styles
fPx = fPt*96.0/72.0 # 1 em in pixels 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 # Scaled Sizes
# ============ # ============
fPt = self._textFont.pointSizeF()
fPx = fPt*96.0/72.0 # 1 em in pixels
mPx = fPx * self._dpi/96.0
self._mHead = { self._mHead = {
BlockTyp.TITLE: (fPx * self._marginTitle[0], fPx * self._marginTitle[1]), BlockTyp.TITLE: (fPx * self._marginTitle[0], fPx * self._marginTitle[1]),
BlockTyp.HEAD1: (fPx * self._marginHead1[0], fPx * self._marginHead1[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._mMeta = (fPx * self._marginMeta[0], fPx * self._marginMeta[1])
self._mSep = (fPx * self._marginSep[0], fPx * self._marginSep[1]) self._mSep = (fPx * self._marginSep[0], fPx * self._marginSep[1])
self._mIndent = fPx * 2.0 self._mIndent = mPx * 2.0
self._tIndent = fPx * self._firstWidth self._tIndent = mPx * self._firstWidth
# Text Formats # Text Formats
# ============ # ============
@@ -248,10 +269,12 @@ class ToQTextDocument(Tokenizer):
def saveDocument(self, path: Path) -> None: def saveDocument(self, path: Path) -> None:
"""Save the document as a PDF file.""" """Save the document as a PDF file."""
m = self._pageMargins m = self._pageMargins
logger.info("Writing PDF at %d DPI", self._dpi)
printer = QPrinter(QPrinter.PrinterMode.HighResolution) printer = QPrinter(QPrinter.PrinterMode.HighResolution)
printer.setDocName(self._project.data.name) printer.setDocName(self._project.data.name)
printer.setCreator(f"novelWriter/{__version__}") printer.setCreator(f"novelWriter/{__version__}")
printer.setResolution(self._dpi)
printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat)
printer.setPageSize(self._pageSize) printer.setPageSize(self._pageSize)
printer.setPageMargins(m.left(), m.top(), m.right(), m.bottom(), QPrinter.Unit.Millimeter) printer.setPageMargins(m.left(), m.top(), m.right(), m.bottom(), QPrinter.Unit.Millimeter)
@@ -318,21 +341,21 @@ class ToQTextDocument(Tokenizer):
# Construct next format # Construct next format
if fmt == TextFmt.B_B: if fmt == TextFmt.B_B:
cFmt.setFontWeight(self._bold) cFmt.setFontWeight(QFont.Weight.Bold)
elif fmt == TextFmt.B_E: elif fmt == TextFmt.B_E:
cFmt.setFontWeight(self._normal) cFmt.setFontWeight(self._dWeight)
elif fmt == TextFmt.I_B: elif fmt == TextFmt.I_B:
cFmt.setFontItalic(True) cFmt.setFontItalic(True)
elif fmt == TextFmt.I_E: elif fmt == TextFmt.I_E:
cFmt.setFontItalic(False) cFmt.setFontItalic(self._dItalic)
elif fmt == TextFmt.D_B: elif fmt == TextFmt.D_B:
cFmt.setFontStrikeOut(True) cFmt.setFontStrikeOut(True)
elif fmt == TextFmt.D_E: elif fmt == TextFmt.D_E:
cFmt.setFontStrikeOut(False) cFmt.setFontStrikeOut(self._dStrike)
elif fmt == TextFmt.U_B: elif fmt == TextFmt.U_B:
cFmt.setFontUnderline(True) cFmt.setFontUnderline(True)
elif fmt == TextFmt.U_E: elif fmt == TextFmt.U_E:
cFmt.setFontUnderline(False) cFmt.setFontUnderline(self._dUnderline)
elif fmt == TextFmt.M_B: elif fmt == TextFmt.M_B:
cFmt.setBackground(self._theme.highlight) cFmt.setBackground(self._theme.highlight)
elif fmt == TextFmt.M_E: elif fmt == TextFmt.M_E:
@@ -376,7 +399,7 @@ class ToQTextDocument(Tokenizer):
cFmt.setAnchorHref(data) cFmt.setAnchorHref(data)
elif fmt == TextFmt.HRF_E: elif fmt == TextFmt.HRF_E:
cFmt.setForeground(primary or self._theme.text) cFmt.setForeground(primary or self._theme.text)
cFmt.setFontUnderline(False) cFmt.setFontUnderline(self._dUnderline)
cFmt.setAnchor(False) cFmt.setAnchor(False)
cFmt.setAnchorHref("") cFmt.setAnchorHref("")
elif fmt == TextFmt.FNOTE: elif fmt == TextFmt.FNOTE:
@@ -409,24 +432,36 @@ class ToQTextDocument(Tokenizer):
def _insertNewPageMarker(self, cursor: QTextCursor) -> None: def _insertNewPageMarker(self, cursor: QTextCursor) -> None:
"""Insert a new page marker.""" """Insert a new page marker."""
if self._newPage: if self._newPage:
cursor.insertHtml("<hr width='100%'>") bgCol = QColor(self._theme.text)
bgCol.setAlphaF(0.1)
fgCol = QColor(self._theme.text)
fgCol.setAlphaF(0.8)
hFmt = cursor.blockFormat() fFmt = QTextFrameFormat()
hFmt.setBottomMargin(0.0) fFmt.setBorderStyle(QTextFrameFormat.BorderStyle.BorderStyle_None)
hFmt.setLineHeight(75.0, QtPropLineHeight) fFmt.setBackground(bgCol)
cursor.setBlockFormat(hFmt) fFmt.setTopMargin(self._mSep[0])
fFmt.setBottomMargin(self._mSep[1])
bFmt = QTextBlockFormat(self._blockFmt) bFmt = QTextBlockFormat(self._blockFmt)
bFmt.setAlignment(QtAlignCenter) bFmt.setAlignment(QtAlignCenter)
bFmt.setTopMargin(0.0) bFmt.setTopMargin(0.0)
bFmt.setLineHeight(75.0, QtPropLineHeight) bFmt.setBottomMargin(0.0)
bFmt.setLineHeight(100.0, QtPropLineHeight)
cFmt = QTextCharFormat(self._charFmt) 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.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.insertText(self._project.localLookup("New Page"), cFmt)
cursor.swap(self._document.rootFrame().lastCursorPosition())
return return
def _genHeadStyle(self, hType: BlockTyp, hKey: str, rFmt: QTextBlockFormat) -> T_TextStyle: 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 hCol = self._colorHeads and hType != BlockTyp.TITLE
cFmt = QTextCharFormat(self._charFmt) cFmt = QTextCharFormat(self._charFmt)
cFmt.setForeground(self._theme.head if hCol else self._theme.text) 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)) cFmt.setFontPointSize(self._sHead.get(hType, 1.0))
if hKey and self._anchors: if hKey and self._anchors:
cFmt.setAnchorNames([hKey]) cFmt.setAnchorNames([hKey])
+8 -4
View File
@@ -53,7 +53,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED 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.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
from novelwriter.core.document import NWDocument from novelwriter.core.document import NWDocument
from novelwriter.enum import ( from novelwriter.enum import (
@@ -348,7 +348,9 @@ class GuiDocEditor(QPlainTextEdit):
SHARED.updateSpellCheckLanguage() SHARED.updateSpellCheckLanguage()
# Set the font. See issues #1862 and #1875. # 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.docHeader.updateFont()
self.docFooter.updateFont() self.docFooter.updateFont()
self.docSearch.updateFont() self.docSearch.updateFont()
@@ -1277,11 +1279,13 @@ class GuiDocEditor(QPlainTextEdit):
"""Process the word counter's finished signal.""" """Process the word counter's finished signal."""
if self._docHandle and self._nwItem: if self._docHandle and self._nwItem:
logger.debug("Updating word count") logger.debug("Updating word count")
needsRefresh = wCount != self._nwItem.wordCount
self._nwItem.setCharCount(cCount) self._nwItem.setCharCount(cCount)
self._nwItem.setWordCount(wCount) self._nwItem.setWordCount(wCount)
self._nwItem.setParaCount(pCount) self._nwItem.setParaCount(pCount)
self._nwItem.notifyToRefresh() if needsRefresh:
self.docFooter.updateWordCount(wCount, False) self._nwItem.notifyToRefresh()
self.docFooter.updateWordCount(wCount, False)
return return
@pyqtSlot() @pyqtSlot()
+1 -1
View File
@@ -219,7 +219,7 @@ class GuiDocViewer(QTextBrowser):
qDoc = ToQTextDocument(SHARED.project) qDoc = ToQTextDocument(SHARED.project)
qDoc.setJustify(CONFIG.doJustify) qDoc.setJustify(CONFIG.doJustify)
qDoc.setDialogHighlight(True) qDoc.setDialogHighlight(True)
qDoc.setFont(CONFIG.textFont) qDoc.setTextFont(CONFIG.textFont)
qDoc.setTheme(self._docTheme) qDoc.setTheme(self._docTheme)
qDoc.initDocument() qDoc.initDocument()
qDoc.setKeywords(True) qDoc.setKeywords(True)
+5 -5
View File
@@ -1051,11 +1051,6 @@ class GuiMain(QMainWindow):
self.initMain() self.initMain()
self.saveDocument() self.saveDocument()
if restart:
SHARED.info(self.tr(
"Some changes will not be applied until novelWriter has been restarted."
))
if tree: if tree:
SHARED.project.tree.refreshAllItems() SHARED.project.tree.refreshAllItems()
@@ -1088,6 +1083,11 @@ class GuiMain(QMainWindow):
self._lastTotalCount = 0 self._lastTotalCount = 0
self._updateStatusWordCount() self._updateStatusWordCount()
if restart:
SHARED.info(self.tr(
"Some changes will not be applied until novelWriter has been restarted."
))
return return
@pyqtSlot() @pyqtSlot()
+6 -5
View File
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED 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.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.configlayout import ( from novelwriter.extensions.configlayout import (
@@ -1281,8 +1281,9 @@ class _FormattingTab(NScrollableForm):
# Text Format # Text Format
# =========== # ===========
self._textFont = QFont() font = QFont()
self._textFont.fromString(self._build.getStr("format.textFont")) font.fromString(self._build.getStr("format.textFont"))
self._textFont = fontMatcher(font)
self.textFont.setText(describeFont(self._textFont)) self.textFont.setText(describeFont(self._textFont))
self.textFont.setCursorPosition(0) self.textFont.setCursorPosition(0)
@@ -1437,9 +1438,9 @@ class _FormattingTab(NScrollableForm):
"""Open the QFontDialog and set a font for the font style.""" """Open the QFontDialog and set a font for the font style."""
font, status = SHARED.getFont(self._textFont, CONFIG.nativeFont) font, status = SHARED.getFont(self._textFont, CONFIG.nativeFont)
if status: if status:
self.textFont.setText(describeFont(font)) self._textFont = fontMatcher(font)
self.textFont.setText(describeFont(self._textFont))
self.textFont.setCursorPosition(0) self.textFont.setCursorPosition(0)
self._textFont = font
return return
@pyqtSlot(int) @pyqtSlot(int)
+38 -9
View File
@@ -27,18 +27,19 @@ from xml.etree import ElementTree as ET
import pytest import pytest
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QMimeData, QUrl
from PyQt5.QtGui import QColor, QDesktopServices, QFontDatabase from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontDatabase, QFontInfo
from novelwriter.common import ( from novelwriter.common import (
NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath, NWConfigParser, checkBool, checkFloat, checkInt, checkIntTuple, checkPath,
checkString, checkStringNone, checkUuid, compact, cssCol, describeFont, checkString, checkStringNone, checkUuid, compact, cssCol,
elide, firstFloat, formatFileFilter, formatInt, formatTime, decodeMimeHandles, describeFont, elide, encodeMimeHandles, firstFloat,
formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, fontMatcher, formatFileFilter, formatInt, formatTime, formatTimeStamp,
isItemClass, isItemLayout, isItemType, isListInstance, isTitleTag, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass,
jsonEncode, makeFileNameSafe, minmax, numberToRoman, openExternalPath, isItemLayout, isItemType, isListInstance, isTitleTag, jsonEncode,
readTextFile, simplified, transferCase, uniqueCompact, xmlElement, makeFileNameSafe, minmax, numberToRoman, openExternalPath, readTextFile,
xmlIndent, xmlSubElem, yesNo simplified, transferCase, uniqueCompact, xmlElement, xmlIndent, xmlSubElem,
yesNo
) )
from tests.mocked import causeOSError from tests.mocked import causeOSError
@@ -528,6 +529,34 @@ def testBaseCommon_describeFont():
assert describeFont(None) == "Error" # type: ignore 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 @pytest.mark.base
def testBaseCommon_jsonEncode(): def testBaseCommon_jsonEncode():
"""Test the jsonEncode function.""" """Test the jsonEncode function."""
+1 -1
View File
@@ -101,7 +101,7 @@ def testBaseError_Handler(qtbot, monkeypatch, nwGUI):
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(NWErrorMessage, "exec", lambda *a: None) mp.setattr(NWErrorMessage, "exec", lambda *a: None)
mp.setattr("PyQt5.QtWidgets.QApplication.exit", 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 exceptionHandler(Exception, "Error Message", None) # type: ignore
nwGUI.closeMain() nwGUI.closeMain()
+1 -1
View File
@@ -110,7 +110,7 @@ def testFmtToken_Setters(mockGUI):
tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True) tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True)
tokens.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", True) tokens.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", True)
tokens.setSectionFormat(f"X: {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.setLineHeight(2.0)
tokens.setBlockIndent(6.0) tokens.setBlockIndent(6.0)
tokens.setJustify(True) tokens.setJustify(True)
+9 -9
View File
@@ -22,7 +22,7 @@ from __future__ import annotations
import pytest import pytest
from PyQt5.QtGui import QTextBlock, QTextCharFormat, QTextCursor from PyQt5.QtGui import QFont, QTextBlock, QTextCharFormat, QTextCursor
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.constants import nwUnicode from novelwriter.constants import nwUnicode
@@ -71,7 +71,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.TITLE][0] assert bFmt.topMargin() == doc._mHead[BlockTyp.TITLE][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.TITLE][1] assert bFmt.bottomMargin() == doc._mHead[BlockTyp.TITLE][1]
cFmt = charFmtInBlock(block, 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.fontPointSize() == doc._sHead[BlockTyp.TITLE]
assert cFmt.foreground().color() == THEME.text assert cFmt.foreground().color() == THEME.text
@@ -82,7 +82,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD1][0] assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD1][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD1][1] assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD1][1]
cFmt = charFmtInBlock(block, 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.fontPointSize() == doc._sHead[BlockTyp.HEAD1]
assert cFmt.foreground().color() == THEME.head assert cFmt.foreground().color() == THEME.head
@@ -93,7 +93,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD2][0] assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD2][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD2][1] assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD2][1]
cFmt = charFmtInBlock(block, 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.fontPointSize() == doc._sHead[BlockTyp.HEAD2]
assert cFmt.foreground().color() == THEME.head assert cFmt.foreground().color() == THEME.head
@@ -104,7 +104,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD3][0] assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD3][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD3][1] assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD3][1]
cFmt = charFmtInBlock(block, 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.fontPointSize() == doc._sHead[BlockTyp.HEAD3]
assert cFmt.foreground().color() == THEME.head assert cFmt.foreground().color() == THEME.head
@@ -115,7 +115,7 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI):
assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD4][0] assert bFmt.topMargin() == doc._mHead[BlockTyp.HEAD4][0]
assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD4][1] assert bFmt.bottomMargin() == doc._mHead[BlockTyp.HEAD4][1]
cFmt = charFmtInBlock(block, 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.fontPointSize() == doc._sHead[BlockTyp.HEAD4]
assert cFmt.foreground().color() == THEME.head assert cFmt.foreground().color() == THEME.head
@@ -478,11 +478,11 @@ def testFmtToQTextDocument_TextCharFormats(mockGUI):
block = doc.document.findBlockByNumber(1) block = doc.document.findBlockByNumber(1)
assert block.text() == "With bold text" assert block.text() == "With bold text"
cFmt = charFmtInBlock(block, 1) cFmt = charFmtInBlock(block, 1)
assert cFmt.fontWeight() == doc._normal assert cFmt.fontWeight() == doc._dWeight
cFmt = charFmtInBlock(block, 6) cFmt = charFmtInBlock(block, 6)
assert cFmt.fontWeight() == doc._bold assert cFmt.fontWeight() == QFont.Weight.Bold
cFmt = charFmtInBlock(block, 10) cFmt = charFmtInBlock(block, 10)
assert cFmt.fontWeight() == doc._normal assert cFmt.fontWeight() == doc._dWeight
# 2: Italic # 2: Italic
block = doc.document.findBlockByNumber(2) block = doc.document.findBlockByNumber(2)
+1 -1
View File
@@ -48,7 +48,7 @@ def testToolManuscript_Init(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
nwGUI.openProject(projPath) nwGUI.openProject(projPath)
SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi") 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) nwGUI.mainMenu.aBuildManuscript.activate(QAction.ActionEvent.Trigger)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiManuscript) is not None, timeout=1000) qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiManuscript) is not None, timeout=1000)