Fix font style and PDF issues (#2122)
This commit is contained in:
+31
-1
@@ -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("|")
|
||||
|
||||
+15
-12
@@ -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
|
||||
|
||||
##
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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("<hr width='100%'>")
|
||||
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])
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user