diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index 28d72c837..a4f8cf166 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -59,6 +59,7 @@ fmt_strike-md = nw_tb-strike-md.svg
fmt_subscript = nw_tb-subscript.svg
fmt_superscript = nw_tb-superscript.svg
fmt_underline = nw_tb-underline.svg
+font = nw_font.svg
forward = typ_chevron-right.svg
import = mixed_import.svg
list = typ_th-list.svg
@@ -78,6 +79,7 @@ proj_scene = mixed_document-scene.svg
proj_section = mixed_document-section.svg
proj_stats = typ_chart-bar-grey.svg
proj_title = mixed_document-title.svg
+quote = nw_quote.svg
refresh = typ_refresh.svg
remove = typ_minus.svg
revert = typ_refresh-flipped.svg
diff --git a/novelwriter/assets/icons/typicons_dark/nw_font.svg b/novelwriter/assets/icons/typicons_dark/nw_font.svg
new file mode 100644
index 000000000..588dd6151
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_font.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/nw_quote.svg b/novelwriter/assets/icons/typicons_dark/nw_quote.svg
new file mode 100644
index 000000000..db50fcb4f
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/nw_quote.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 1af2ee60d..022f417ea 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -59,6 +59,7 @@ fmt_strike-md = nw_tb-strike-md.svg
fmt_subscript = nw_tb-subscript.svg
fmt_superscript = nw_tb-superscript.svg
fmt_underline = nw_tb-underline.svg
+font = nw_font.svg
forward = typ_chevron-right.svg
import = mixed_import.svg
list = typ_th-list.svg
@@ -78,6 +79,7 @@ proj_scene = mixed_document-scene.svg
proj_section = mixed_document-section.svg
proj_stats = typ_chart-bar-grey.svg
proj_title = mixed_document-title.svg
+quote = nw_quote.svg
refresh = typ_refresh.svg
remove = typ_minus.svg
revert = typ_refresh-flipped.svg
diff --git a/novelwriter/assets/icons/typicons_light/nw_font.svg b/novelwriter/assets/icons/typicons_light/nw_font.svg
new file mode 100644
index 000000000..987f2fc6e
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_font.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/nw_quote.svg b/novelwriter/assets/icons/typicons_light/nw_quote.svg
new file mode 100644
index 000000000..bdf76e817
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/nw_quote.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/novelwriter/common.py b/novelwriter/common.py
index 70c4ce273..0ef075419 100644
--- a/novelwriter/common.py
+++ b/novelwriter/common.py
@@ -405,7 +405,9 @@ def describeFont(font: QFont) -> str:
"""Describe a font in a way that can be displayed on the GUI."""
if isinstance(font, QFont):
info = QFontInfo(font)
- return f"{font.family()} {info.styleName()} @ {font.pointSize()} pt"
+ family = info.family()
+ styles = [v for v in info.styleName().split() if v not in family]
+ return " ".join([f"{info.pointSize()} pt", family] + styles)
return "Error"
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 2bff5d2bc..148e75d48 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -114,6 +114,7 @@ def __init__(self) -> None:
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
+ self.nativeFont = True # Use native font dialog
# Size Settings
self._mainWinSize = [1200, 650] # Last size of the main GUI window
@@ -598,6 +599,7 @@ def loadConfig(self) -> bool:
self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
+ self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
self._lastPath = conf.rdPath(sec, "lastpath", self._lastPath)
# Sizes
@@ -707,6 +709,7 @@ def saveConfig(self) -> bool:
"hidevscroll": str(self.hideVScroll),
"hidehscroll": str(self.hideHScroll),
"lastnotes": str(self.lastNotes),
+ "nativefont": str(self.nativeFont),
"lastpath": str(self._lastPath),
}
diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py
index f7392e26b..aa295b6b2 100644
--- a/novelwriter/core/buildsettings.py
+++ b/novelwriter/core/buildsettings.py
@@ -76,12 +76,12 @@
"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),
"format.replaceTabs": (bool, False),
+ "format.keepBreaks": (bool, True),
"format.firstLineIndent": (bool, False),
"format.firstIndentWidth": (float, 1.4),
"format.indentFirstPar": (bool, False),
@@ -96,7 +96,6 @@
"odt.addColours": (bool, True),
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
"odt.pageCountOffset": (int, 0),
- "md.preserveBreaks": (bool, True),
"html.addStyles": (bool, True),
"html.preserveTabs": (bool, False),
}
@@ -125,13 +124,13 @@
"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"),
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
+ "format.keepBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
"format.grpParIndent": QT_TRANSLATE_NOOP("Builds", "First Line Indent"),
"format.firstLineIndent": QT_TRANSLATE_NOOP("Builds", "Enable Indent"),
@@ -153,9 +152,6 @@
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
- "md": QT_TRANSLATE_NOOP("Builds", "Markdown (.md)"),
- "md.preserveBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
-
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index c65ac0be0..1b5350a89 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -28,7 +28,7 @@
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
@@ -216,16 +216,10 @@ def iterBuildMarkdown(self, path: Path, extendedMd: bool) -> Iterable[tuple[int,
makeObj = ToMarkdown(self._project)
filtered = self._setupBuild(makeObj)
- if extendedMd:
- makeObj.setExtendedMarkdown()
- else:
- makeObj.setStandardMarkdown()
-
+ makeObj.setExtendedMarkdown(extendedMd)
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
- makeObj.setPreserveBreaks(self._build.getBool("md.preserveBreaks"))
-
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
@@ -285,13 +279,9 @@ def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tupl
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"),
@@ -330,9 +320,9 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
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"))
bldObj.setFirstLineIndent(
self._build.getBool("format.firstLineIndent"),
self._build.getFloat("format.firstIndentWidth"),
diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py
index 85a91f594..f16cc515c 100644
--- a/novelwriter/core/tohtml.py
+++ b/novelwriter/core/tohtml.py
@@ -34,6 +34,7 @@
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
+from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__)
@@ -373,8 +374,16 @@ def getStyleSheet(self) -> list[str]:
mScale = self._lineHeight/1.15
styles = []
- styles.append("body {{font-family: '{0:s}'; font-size: {1:d}pt;}}".format(
- self._textFont, self._textSize
+ font = self._textFont
+ styles.append((
+ "body {{"
+ "font-family: '{0:s}'; font-size: {1:d}pt; "
+ "font-weight: {2:d}; font-style: {3:s};"
+ "}}"
+ ).format(
+ font.family(), font.pointSize(),
+ FONT_WEIGHTS.get(font.weight(), 400),
+ FONT_STYLE.get(font.style(), "normal"),
))
styles.append((
"p {{"
diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py
index 448cb2ed5..72e684497 100644
--- a/novelwriter/core/tokenizer.py
+++ b/novelwriter/core/tokenizer.py
@@ -34,6 +34,7 @@
from time import time
from PyQt5.QtCore import QCoreApplication, QRegularExpression
+from PyQt5.QtGui import QFont
from novelwriter.common import checkInt, formatTimeStamp, numberToRoman
from novelwriter.constants import (
@@ -139,9 +140,7 @@ def __init__(self, project: NWProject) -> None:
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 = QFont("Serif", 11) # 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 +314,9 @@ def setSceneStyle(self, center: bool, pageBreak: bool) -> None:
)
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/core/tomarkdown.py b/novelwriter/core/tomarkdown.py
index 440da0e06..eccf2300e 100644
--- a/novelwriter/core/tomarkdown.py
+++ b/novelwriter/core/tomarkdown.py
@@ -81,15 +81,11 @@ class ToMarkdown(Tokenizer):
supports concatenating novelWriter markup files.
"""
- M_STD = 0 # Standard Markdown
- M_EXT = 1 # Extended Markdown
-
def __init__(self, project: NWProject) -> None:
super().__init__(project)
- self._genMode = self.M_STD
self._fullMD: list[str] = []
- self._preserveBreaks = True
self._usedNotes: dict[str, int] = {}
+ self._extended = True
return
##
@@ -105,19 +101,9 @@ def fullMD(self) -> list[str]:
# Setters
##
- def setStandardMarkdown(self) -> None:
- """Set the converter to use standard Markdown formatting."""
- self._genMode = self.M_STD
- return
-
- def setExtendedMarkdown(self) -> None:
+ def setExtendedMarkdown(self, state: bool) -> None:
"""Set the converter to use Extended Markdown formatting."""
- self._genMode = self.M_EXT
- return
-
- def setPreserveBreaks(self, state: bool) -> None:
- """Preserve line breaks in paragraphs."""
- self._preserveBreaks = state
+ self._extended = state
return
##
@@ -132,12 +118,12 @@ def doConvert(self) -> None:
"""Convert the list of text tokens into a Markdown document."""
self._result = ""
- if self._genMode == self.M_STD:
- mTags = STD_MD
- cSkip = ""
- else:
+ if self._extended:
mTags = EXT_MD
cSkip = nwUnicode.U_MMSP
+ else:
+ mTags = STD_MD
+ cSkip = ""
lines = []
for tType, _, tText, tFormat, tStyle in self._tokens:
@@ -195,7 +181,7 @@ def doConvert(self) -> None:
def appendFootnotes(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
- tags = STD_MD if self._genMode == self.M_STD else EXT_MD
+ tags = EXT_MD if self._extended else STD_MD
footnotes = self._localLookup("Footnotes")
lines = []
diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py
index cd81d100e..9c631345b 100644
--- a/novelwriter/core/toodt.py
+++ b/novelwriter/core/toodt.py
@@ -35,11 +35,14 @@
from pathlib import Path
from zipfile import ZipFile
+from PyQt5.QtGui import QFont
+
from novelwriter import __version__
from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
+from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__)
@@ -108,6 +111,10 @@ def _mkTag(ns: str, tag: str) -> str:
S_META = "Text_20_Meta"
S_HNF = "Header_20_and_20_Footer"
+# Font Data
+FONT_WEIGHT_NUM = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]
+FONT_WEIGHT_MAP = {"400": "normal", "700": "bold"}
+
class ToOdt(Tokenizer):
"""Core: Open Document Writer
@@ -149,16 +156,18 @@ def __init__(self, project: NWProject, isFlat: bool) -> None:
self._errData = [] # List of errors encountered
# Properties
- self._textFont = "Liberation Serif"
- self._textSize = 12
- self._textFixed = False
+ self._textFont = QFont("Liberation Serif", 12)
self._colourHead = False
self._headerFormat = ""
self._pageOffset = 0
# Internal
- self._fontFamily = "'Liberation Serif'"
+ self._fontFamily = "Liberation Serif"
+ self._fontSize = 12
+ self._fontWeight = "normal"
+ self._fontStyle = "normal"
self._fontPitch = "variable"
+ self._fontBold = "bold"
self._fSizeTitle = "30pt"
self._fSizeHead1 = "24pt"
self._fSizeHead2 = "20pt"
@@ -260,19 +269,25 @@ def initDocument(self) -> None:
# Initialise Variables
# ====================
- self._fontFamily = self._textFont
- if len(self._textFont.split()) > 1:
- self._fontFamily = f"'{self._textFont}'"
- self._fontPitch = "fixed" if self._textFixed else "variable"
-
- self._fSizeTitle = f"{round(2.50 * self._textSize):d}pt"
- self._fSizeHead1 = f"{round(2.00 * self._textSize):d}pt"
- self._fSizeHead2 = f"{round(1.60 * self._textSize):d}pt"
- self._fSizeHead3 = f"{round(1.30 * self._textSize):d}pt"
- self._fSizeHead4 = f"{round(1.15 * self._textSize):d}pt"
- self._fSizeHead = f"{round(1.15 * self._textSize):d}pt"
- self._fSizeText = f"{self._textSize:d}pt"
- self._fSizeFoot = f"{round(0.8*self._textSize):d}pt"
+ intWeight = FONT_WEIGHTS.get(self._textFont.weight(), 400)
+ fontWeight = str(intWeight)
+ fontBold = str(min(intWeight + 300, 900))
+
+ 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._fSizeTitle = f"{round(2.50 * self._fontSize):d}pt"
+ self._fSizeHead1 = f"{round(2.00 * self._fontSize):d}pt"
+ self._fSizeHead2 = f"{round(1.60 * self._fontSize):d}pt"
+ self._fSizeHead3 = f"{round(1.30 * self._fontSize):d}pt"
+ self._fSizeHead4 = f"{round(1.15 * self._fontSize):d}pt"
+ self._fSizeHead = f"{round(1.15 * self._fontSize):d}pt"
+ self._fSizeText = f"{self._fontSize:d}pt"
+ self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt"
mScale = self._lineHeight/1.15
@@ -320,7 +335,7 @@ def initDocument(self) -> None:
tAttr[_mkTag("office", "version")] = X_VERS
fAttr = {}
- fAttr[_mkTag("style", "name")] = self._textFont
+ fAttr[_mkTag("style", "name")] = self._fontFamily
fAttr[_mkTag("style", "font-pitch")] = self._fontPitch
if self._isFlat:
@@ -726,7 +741,7 @@ def _textStyle(self, hFmt: int) -> str:
style = ODTTextStyle(f"T{len(self._autoText)+1:d}")
if hFmt & X_BLD:
- style.setFontWeight("bold")
+ style.setFontWeight(self._fontBold)
if hFmt & X_ITA:
style.setFontStyle("italic")
if hFmt & X_DEL:
@@ -764,7 +779,7 @@ def _generateFootnote(self, key: str) -> ET.Element | None:
def _emToCm(self, value: float) -> str:
"""Converts an em value to centimetres."""
- return f"{value*2.54/72*self._textSize:.3f}cm"
+ return f"{value*2.54/72*self._fontSize:.3f}cm"
##
# Style Elements
@@ -808,8 +823,10 @@ def _defaultStyles(self) -> None:
_mkTag("style", "writing-mode"): "page",
})
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
- _mkTag("style", "font-name"): self._textFont,
+ _mkTag("style", "font-name"): self._fontFamily,
_mkTag("fo", "font-family"): self._fontFamily,
+ _mkTag("fo", "font-weight"): self._fontWeight,
+ _mkTag("fo", "font-style"): self._fontStyle,
_mkTag("fo", "font-size"): self._fSizeText,
_mkTag("fo", "language"): self._dLanguage,
_mkTag("fo", "country"): self._dCountry,
@@ -822,8 +839,10 @@ def _defaultStyles(self) -> None:
_mkTag("style", "class"): "text",
})
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
- _mkTag("style", "font-name"): self._textFont,
+ _mkTag("style", "font-name"): self._fontFamily,
_mkTag("fo", "font-family"): self._fontFamily,
+ _mkTag("fo", "font-weight"): self._fontWeight,
+ _mkTag("fo", "font-style"): self._fontStyle,
_mkTag("fo", "font-size"): self._fSizeText,
})
@@ -841,8 +860,10 @@ def _defaultStyles(self) -> None:
_mkTag("fo", "keep-with-next"): "always",
})
ET.SubElement(xStyl, _mkTag("style", "text-properties"), attrib={
- _mkTag("style", "font-name"): self._textFont,
+ _mkTag("style", "font-name"): self._fontFamily,
_mkTag("fo", "font-family"): self._fontFamily,
+ _mkTag("fo", "font-weight"): self._fontWeight,
+ _mkTag("fo", "font-style"): self._fontStyle,
_mkTag("fo", "font-size"): self._fSizeHead,
})
@@ -868,9 +889,10 @@ def _useableStyles(self) -> None:
style.setMarginBottom(self._mBotText)
style.setLineHeight(self._fLineHeight)
style.setTextAlign(self._textAlign)
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeText)
+ style.setFontWeight(self._fontWeight)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
@@ -891,9 +913,10 @@ def _useableStyles(self) -> None:
style.setMarginTop(self._mTopMeta)
style.setMarginBottom(self._mBotMeta)
style.setLineHeight(self._fLineHeight)
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeText)
+ style.setFontWeight(self._fontWeight)
style.setColour(self._colMetaTx)
style.setOpacity(self._opaMetaTx)
style.packXML(self._xStyl)
@@ -908,10 +931,10 @@ def _useableStyles(self) -> None:
style.setMarginTop(self._mTopTitle)
style.setMarginBottom(self._mBotTitle)
style.setTextAlign("center")
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeTitle)
- style.setFontWeight("bold")
+ style.setFontWeight(self._fontBold)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
@@ -925,9 +948,10 @@ def _useableStyles(self) -> None:
style.setMarginBottom(self._mBotText)
style.setLineHeight(self._fLineHeight)
style.setTextAlign("center")
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeText)
+ style.setFontWeight(self._fontWeight)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
@@ -940,10 +964,10 @@ def _useableStyles(self) -> None:
style.setClass("text")
style.setMarginTop(self._mTopHead1)
style.setMarginBottom(self._mBotHead1)
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead1)
- style.setFontWeight("bold")
+ style.setFontWeight(self._fontBold)
style.setColour(self._colHead12)
style.setOpacity(self._opaHead12)
style.packXML(self._xStyl)
@@ -958,10 +982,10 @@ def _useableStyles(self) -> None:
style.setClass("text")
style.setMarginTop(self._mTopHead2)
style.setMarginBottom(self._mBotHead2)
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead2)
- style.setFontWeight("bold")
+ style.setFontWeight(self._fontBold)
style.setColour(self._colHead12)
style.setOpacity(self._opaHead12)
style.packXML(self._xStyl)
@@ -976,10 +1000,10 @@ def _useableStyles(self) -> None:
style.setClass("text")
style.setMarginTop(self._mTopHead3)
style.setMarginBottom(self._mBotHead3)
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead3)
- style.setFontWeight("bold")
+ style.setFontWeight(self._fontBold)
style.setColour(self._colHead34)
style.setOpacity(self._opaHead34)
style.packXML(self._xStyl)
@@ -994,10 +1018,10 @@ def _useableStyles(self) -> None:
style.setClass("text")
style.setMarginTop(self._mTopHead4)
style.setMarginBottom(self._mBotHead4)
- style.setFontName(self._textFont)
+ style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead4)
- style.setFontWeight("bold")
+ style.setFontWeight(self._fontBold)
style.setColour(self._colHead34)
style.setOpacity(self._opaHead34)
style.packXML(self._xStyl)
@@ -1077,7 +1101,7 @@ class ODTParagraphStyle:
VALID_BREAK = ["auto", "column", "page", "even-page", "odd-page", "inherit"]
VALID_LEVEL = ["1", "2", "3", "4"]
VALID_CLASS = ["text", "chapter", "extra"]
- VALID_WEIGHT = ["normal", "inherit", "bold"]
+ VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
def __init__(self, name: str) -> None:
@@ -1320,8 +1344,8 @@ class ODTTextStyle:
Only the used settings are exposed here to keep the class minimal
and fast.
"""
- VALID_WEIGHT = ["normal", "inherit", "bold"]
- VALID_STYLE = ["normal", "inherit", "italic"]
+ VALID_WEIGHT = ["normal", "bold"] + FONT_WEIGHT_NUM
+ VALID_STYLE = ["normal", "italic", "oblique"]
VALID_POS = ["super", "sub"]
VALID_LSTYLE = ["none", "solid"]
VALID_LTYPE = ["single", "double"]
diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py
index 3158fa24c..f1e6bb85d 100644
--- a/novelwriter/dialogs/preferences.py
+++ b/novelwriter/dialogs/preferences.py
@@ -30,8 +30,7 @@
from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence
from PyQt5.QtWidgets import (
QAbstractButton, QApplication, QCompleter, QDialog, QDialogButtonBox,
- QFileDialog, QFontDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout,
- QWidget
+ QFileDialog, QHBoxLayout, QLineEdit, QPushButton, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -183,7 +182,7 @@ def buildForm(self) -> None:
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,
@@ -207,6 +206,14 @@ def buildForm(self) -> None:
self.tr("Scrolling available with mouse wheel and keys only.")
)
+ # Native Font Dialog
+ self.nativeFont = NSwitch(self)
+ self.nativeFont.setChecked(CONFIG.nativeFont)
+ self.mainForm.addRow(
+ self.tr("Use the system's font selection dialog"), self.nativeFont,
+ self.tr("Turn off to use the Qt font dialog, which may have more options.")
+ )
+
# Document Style
# ==============
@@ -233,7 +240,7 @@ def buildForm(self) -> None:
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 +702,7 @@ def buildForm(self) -> None:
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 +716,7 @@ def buildForm(self) -> None:
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 +731,7 @@ def buildForm(self) -> None:
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 +745,7 @@ def buildForm(self) -> None:
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"],
@@ -804,7 +811,7 @@ def _gotoSearch(self) -> None:
@pyqtSlot()
def _selectGuiFont(self) -> None:
"""Open the QFontDialog and set a font for the font style."""
- font, status = QFontDialog.getFont(self._guiFont, self)
+ font, status = SHARED.getFont(self._guiFont, self.nativeFont.isChecked())
if status:
self.guiFont.setText(describeFont(font))
self.guiFont.setCursorPosition(0)
@@ -814,7 +821,7 @@ def _selectGuiFont(self) -> None:
@pyqtSlot()
def _selectTextFont(self) -> None:
"""Open the QFontDialog and set a font for the font style."""
- font, status = QFontDialog.getFont(CONFIG.textFont, self)
+ font, status = SHARED.getFont(self._textFont, self.nativeFont.isChecked())
if status:
self.textFont.setText(describeFont(font))
self.textFont.setCursorPosition(0)
@@ -883,6 +890,7 @@ def _saveValues(self) -> None:
CONFIG.guiTheme = guiTheme
CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked()
+ CONFIG.nativeFont = self.nativeFont.isChecked()
CONFIG.setGuiFont(self._guiFont)
# Document Style
diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py
index 12bc0b0b5..760e82d64 100644
--- a/novelwriter/dialogs/quotes.py
+++ b/novelwriter/dialogs/quotes.py
@@ -50,6 +50,7 @@ def __init__(self, parent: QWidget, current: str = '"') -> None:
logger.debug("Create: GuiQuoteSelect")
self.setObjectName("GuiQuoteSelect")
+ self.setWindowTitle(self.tr("Select Quote Style"))
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index a986f86aa..72a951783 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -501,9 +501,9 @@ class GuiIcons:
# General Button Icons
"add", "add_document", "backward", "bookmark", "browse", "checked", "close", "cross",
- "document", "down", "edit", "export", "forward", "import", "list", "maximise", "menu",
- "minimise", "more", "noncheckable", "open", "panel", "refresh", "remove", "revert",
- "search_replace", "search", "settings", "star", "unchecked", "up", "view",
+ "document", "down", "edit", "export", "font", "forward", "import", "list", "maximise",
+ "menu", "minimise", "more", "noncheckable", "open", "panel", "quote", "refresh", "remove",
+ "revert", "search_replace", "search", "settings", "star", "unchecked", "up", "view",
# Switches
"sticky-on", "sticky-off",
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index e703eb2cf..d6c19a9b5 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -31,7 +31,8 @@
from typing import TYPE_CHECKING, TypeVar
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
-from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
+from PyQt5.QtGui import QFont
+from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
@@ -255,8 +256,11 @@ def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None:
QThreadPool.globalInstance().start(runnable, priority=priority)
return
- def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
- allowZip: bool = False) -> Path | None:
+ def getProjectPath(
+ self, parent: QWidget,
+ path: str | Path | None = None,
+ allowZip: bool = False
+ ) -> Path | None:
"""Open the file dialog and select a novelWriter project file."""
label = (self.tr("novelWriter Project File or Zip File")
if allowZip else self.tr("novelWriter Project File"))
@@ -267,6 +271,13 @@ def getProjectPath(self, parent: QWidget, path: str | Path | None = None,
)
return Path(selected) if selected else None
+ def getFont(self, current: QFont, native: bool) -> tuple[QFont, bool]:
+ """Open the font dialog and select a font."""
+ kwargs = {}
+ if not native:
+ kwargs["options"] = QFontDialog.FontDialogOption.DontUseNativeDialog
+ return QFontDialog.getFont(current, self.mainGui, self.tr("Select Font"), **kwargs)
+
def findTopLevelWidget(self, kind: type[NWWidget]) -> NWWidget | None:
"""Find a top level widget."""
for widget in self.mainGui.children():
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index ca19e6d08..7e27b8679 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -31,7 +31,7 @@
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,
@@ -402,12 +402,12 @@ def _printDocument(self) -> None:
def _updatePreview(self, data: dict, build: BuildSettings) -> None:
"""Update the preview widget and set relevant values."""
+ textFont = QFont()
+ textFont.fromString(build.getStr("format.textFont"))
+
self.docPreview.setContent(data)
self.docPreview.setBuildName(build.name)
- self.docPreview.setTextFont(
- build.getStr("format.textFont"),
- build.getInt("format.textSize")
- )
+ self.docPreview.setTextFont(textFont)
self.docPreview.setJustify(
build.getBool("format.justifyText")
)
@@ -787,7 +787,7 @@ def __init__(self, parent: QWidget) -> None:
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 @@ def setJustify(self, state: bool) -> None:
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 dca739f9b..1488c3b81 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -30,13 +30,14 @@
from PyQt5.QtCore import QEvent, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFont, QIcon, QSyntaxHighlighter, QTextCharFormat, QTextDocument
from PyQt5.QtWidgets import (
- QAbstractButton, QAbstractItemView, QDialogButtonBox, QFontDialog, QFrame,
- QGridLayout, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMenu,
- QPlainTextEdit, QPushButton, QSplitter, QStackedWidget, QTreeWidget,
- QTreeWidgetItem, QVBoxLayout, QWidget
+ QAbstractButton, QAbstractItemView, QDialogButtonBox, QFrame, QGridLayout,
+ QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMenu, QPlainTextEdit,
+ QPushButton, QSplitter, QStackedWidget, QTreeWidget, QTreeWidgetItem,
+ QVBoxLayout, QWidget
)
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 @@ def __init__(self, parent: QWidget, build: BuildSettings) -> None:
self._build = build
self._unitScale = 1.0
+ self._textFont = QFont(CONFIG.textFont)
iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
@@ -1063,24 +1065,16 @@ def __init__(self, parent: QWidget, build: BuildSettings) -> None:
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)
@@ -1098,10 +1092,12 @@ def __init__(self, parent: QWidget, build: BuildSettings) -> None:
self.justifyText = NSwitch(self, height=iPx)
self.stripUnicode = NSwitch(self, height=iPx)
self.replaceTabs = NSwitch(self, height=iPx)
+ self.keepBreaks = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("format.justifyText"), self.justifyText)
self.addRow(self._build.getLabel("format.stripUnicode"), self.stripUnicode)
self.addRow(self._build.getLabel("format.replaceTabs"), self.replaceTabs)
+ self.addRow(self._build.getLabel("format.keepBreaks"), self.keepBreaks)
# First Line Indent
# =================
@@ -1173,17 +1169,17 @@ def __init__(self, parent: QWidget, build: BuildSettings) -> None:
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(textFont)
- self.textSize.setValue(self._build.getInt("format.textSize"))
- self.lineHeight.setValue(self._build.getFloat("format.lineHeight"))
+ self.textFont.setText(describeFont(self._textFont))
+ self.textFont.setCursorPosition(0)
+ 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"))
+ self.keepBreaks.setChecked(self._build.getBool("format.keepBreaks"))
self.firstIndent.setChecked(self._build.getBool("format.firstLineIndent"))
self.indentWidth.setValue(self._build.getFloat("format.firstIndentWidth"))
@@ -1216,13 +1212,13 @@ def loadContent(self) -> None:
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())
self._build.setValue("format.stripUnicode", self.stripUnicode.isChecked())
self._build.setValue("format.replaceTabs", self.replaceTabs.isChecked())
+ self._build.setValue("format.keepBreaks", self.keepBreaks.isChecked())
self._build.setValue("format.firstLineIndent", self.firstIndent.isChecked())
self._build.setValue("format.firstIndentWidth", self.indentWidth.value())
@@ -1245,13 +1241,11 @@ def saveContent(self) -> None:
@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 = SHARED.getFont(self._textFont, CONFIG.nativeFont)
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)
@@ -1379,12 +1373,6 @@ def __init__(self, parent: QWidget, build: BuildSettings) -> None:
self.htmlPreserveTabs = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("html.preserveTabs"), self.htmlPreserveTabs)
- # Markdown Document
- self.addGroupLabel(self._build.getLabel("md"))
-
- self.mdPreserveBreaks = NSwitch(self, height=iPx)
- self.addRow(self._build.getLabel("md.preserveBreaks"), self.mdPreserveBreaks)
-
# Finalise
self.finalise()
@@ -1397,7 +1385,6 @@ def loadContent(self) -> None:
self.odtPageCountOffset.setValue(self._build.getInt("odt.pageCountOffset"))
self.htmlAddStyles.setChecked(self._build.getBool("html.addStyles"))
self.htmlPreserveTabs.setChecked(self._build.getBool("html.preserveTabs"))
- self.mdPreserveBreaks.setChecked(self._build.getBool("md.preserveBreaks"))
self.odtPageHeader.setCursorPosition(0)
return
@@ -1408,7 +1395,6 @@ def saveContent(self) -> None:
self._build.setValue("odt.pageCountOffset", self.odtPageCountOffset.value())
self._build.setValue("html.addStyles", self.htmlAddStyles.isChecked())
self._build.setValue("html.preserveTabs", self.htmlPreserveTabs.isChecked())
- self._build.setValue("md.preserveBreaks", self.mdPreserveBreaks.isChecked())
return
##
diff --git a/novelwriter/types.py b/novelwriter/types.py
index e4051f8ef..c70d28934 100644
--- a/novelwriter/types.py
+++ b/novelwriter/types.py
@@ -24,7 +24,7 @@
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,23 @@
# 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_STYLE: dict[int, str] = {
+ QFont.Style.StyleNormal: "normal",
+ QFont.Style.StyleItalic: "italic",
+ QFont.Style.StyleOblique: "oblique",
+}
diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
index 99559ed87..0554bd6dc 100644
--- a/tests/reference/baseConfig_novelwriter.conf
+++ b/tests/reference/baseConfig_novelwriter.conf
@@ -9,6 +9,7 @@ localisation = en_GB
hidevscroll = False
hidehscroll = False
lastnotes = 0x0
+nativefont = True
lastpath =
[Sizes]
diff --git a/tests/reference/coreToOdt_SaveFlat_document.fodt b/tests/reference/coreToOdt_SaveFlat_document.fodt
index 78c9ab59a..cee2d9b59 100644
--- a/tests/reference/coreToOdt_SaveFlat_document.fodt
+++ b/tests/reference/coreToOdt_SaveFlat_document.fodt
@@ -1,13 +1,13 @@
- 2024-04-24T18:30:38
- novelWriter/2.5a2
+ 2024-05-22T23:05:27
+ novelWriter/2.5a4
Jane Smith
1234
P42DT12H34M56S
Test Project
- 2024-04-24T18:30:38
+ 2024-05-22T23:05:27
Jane Smith
@@ -16,50 +16,50 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/tests/reference/coreToOdt_SaveFull_styles.xml b/tests/reference/coreToOdt_SaveFull_styles.xml
index 03395db14..809732e0e 100644
--- a/tests/reference/coreToOdt_SaveFull_styles.xml
+++ b/tests/reference/coreToOdt_SaveFull_styles.xml
@@ -6,50 +6,50 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm
index 60d3800bc..e3e8da375 100644
--- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm
+++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm
@@ -5,7 +5,7 @@
Lorem Ipsum
-
+
-
+
-
+
@@ -43,7 +43,7 @@
-
+
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index dba88e9ba..5bf2d1db2 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -492,7 +492,8 @@ def testBaseCommon_describeFont():
"""Test the describeFont function."""
fontDB = QFontDatabase()
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
- assert font.family() in describeFont(font)
+ font.setPointSize(12)
+ assert describeFont(font).startswith("12 pt")
assert describeFont(None) == "Error" # type: ignore
diff --git a/tests/test_core/test_core_buildsettings.py b/tests/test_core/test_core_buildsettings.py
index df891745d..d08bd6c42 100644
--- a/tests/test_core/test_core_buildsettings.py
+++ b/tests/test_core/test_core_buildsettings.py
@@ -148,7 +148,7 @@ def testCoreBuildSettings_BuildValues():
build = BuildSettings()
strSetting = "headings.fmtTitle"
- intSetting = "format.textSize"
+ intSetting = "odt.pageCountOffset"
boolSetting = "filter.includeNovel"
floatSetting = "format.lineHeight"
diff --git a/tests/test_core/test_core_docbuild.py b/tests/test_core/test_core_docbuild.py
index 57c142513..0ac4d00de 100644
--- a/tests/test_core/test_core_docbuild.py
+++ b/tests/test_core/test_core_docbuild.py
@@ -58,8 +58,7 @@
"text.includeBodyText": True,
"text.addNoteHeadings": True,
"format.buildLang": "en_GB",
- "format.textFont": "Arial",
- "format.textSize": 12,
+ "format.textFont": "Arial,12",
"format.lineHeight": 1.5,
"format.justifyText": True,
"format.stripUnicode": False,
diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py
index 2cea33282..c02e833c7 100644
--- a/tests/test_core/test_core_tohtml.py
+++ b/tests/test_core/test_core_tohtml.py
@@ -316,7 +316,6 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# Title
html._tokens = [
(html.T_TITLE, 1, "A Title", [], html.A_PBB | html.A_CENTRE),
- (html.T_EMPTY, 1, "", [], html.A_NONE),
]
html.doConvert()
assert html.result == (
@@ -327,7 +326,6 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# Unnumbered
html._tokens = [
(html.T_HEAD2, 1, "Prologue", [], html.A_PBB),
- (html.T_EMPTY, 1, "", [], html.A_NONE),
]
html.doConvert()
assert html.result == (
@@ -341,7 +339,6 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# Separator
html._tokens = [
(html.T_SEP, 1, "* * *", [], html.A_CENTRE),
- (html.T_EMPTY, 1, "", [], html.A_NONE),
]
html.doConvert()
assert html.result == "* * *
\n"
@@ -349,7 +346,6 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# Skip
html._tokens = [
(html.T_SKIP, 1, "", [], html.A_NONE),
- (html.T_EMPTY, 1, "", [], html.A_NONE),
]
html.doConvert()
assert html.result == "
\n"
@@ -425,8 +421,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# Indent Left
html._tokens = [
- (html.T_TEXT, 1, "Some text ...", [], html.A_IND_L),
- (html.T_EMPTY, 2, "", [], html.A_NONE),
+ (html.T_TEXT, 1, "Some text ...", [], html.A_IND_L),
]
html.doConvert()
assert html.result == (
@@ -435,8 +430,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
# Indent Right
html._tokens = [
- (html.T_TEXT, 1, "Some text ...", [], html.A_IND_R),
- (html.T_EMPTY, 2, "", [], html.A_NONE),
+ (html.T_TEXT, 1, "Some text ...", [], html.A_IND_R),
]
html.doConvert()
assert html.result == (
diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py
index 388b8843a..39e37b891 100644
--- a/tests/test_core/test_core_tokenizer.py
+++ b/tests/test_core/test_core_tokenizer.py
@@ -24,6 +24,8 @@
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
@@ -1835,7 +1833,7 @@ def testCoreToken_SceneSeparators(mockGUI):
# ===========================
# Requires a fresh builder class
md = ToMarkdown(project)
- md.setExtendedMarkdown()
+ md.setExtendedMarkdown(True)
md._isNovel = True
md._text = (
diff --git a/tests/test_core/test_core_tomarkdown.py b/tests/test_core/test_core_tomarkdown.py
index 8547f7781..8ef04caff 100644
--- a/tests/test_core/test_core_tomarkdown.py
+++ b/tests/test_core/test_core_tomarkdown.py
@@ -82,7 +82,7 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI):
toMD._isFirst = True
# Text for Extended Markdown
- toMD.setExtendedMarkdown()
+ toMD.setExtendedMarkdown(True)
toMD._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
toMD.tokenizeText()
toMD.doConvert()
@@ -91,7 +91,7 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI):
)
# Text for Standard Markdown
- toMD.setStandardMarkdown()
+ toMD.setExtendedMarkdown(False)
toMD._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
toMD.tokenizeText()
toMD.doConvert()
@@ -100,7 +100,7 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI):
)
# Shortcodes for Extended Markdown
- toMD.setExtendedMarkdown()
+ toMD.setExtendedMarkdown(True)
toMD._text = (
"Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], "
"super[sup]script[/sup], sub[sub]script[/sub] here\n"
@@ -113,7 +113,7 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI):
)
# Shortcodes for Standard Markdown
- toMD.setStandardMarkdown()
+ toMD.setExtendedMarkdown(False)
toMD._text = (
"Some [b]bold[/b], [i]italic[/i], [s]strike[/s], [u]underline[/u], [m]mark[/m], "
"super[sup]script[/sup], sub[sub]script[/sub] here\n"
@@ -215,6 +215,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
toMD = ToMarkdown(project)
toMD._isNovel = True
+ toMD.setExtendedMarkdown(False)
# Special Titles
# ==============
@@ -222,7 +223,6 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
# Title
toMD._tokens = [
(toMD.T_TITLE, 1, "A Title", [], toMD.A_PBB | toMD.A_CENTRE),
- (toMD.T_EMPTY, 1, "", [], toMD.A_NONE),
]
toMD.doConvert()
assert toMD.result == "# A Title\n\n"
@@ -233,7 +233,6 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
# Separator
toMD._tokens = [
(toMD.T_SEP, 1, "* * *", [], toMD.A_CENTRE),
- (toMD.T_EMPTY, 1, "", [], toMD.A_NONE),
]
toMD.doConvert()
assert toMD.result == "* * *\n\n"
@@ -241,7 +240,6 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
# Skip
toMD._tokens = [
(toMD.T_SKIP, 1, "", [], toMD.A_NONE),
- (toMD.T_EMPTY, 1, "", [], toMD.A_NONE),
]
toMD.doConvert()
assert toMD.result == "\n\n"
diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py
index 19f2fd20d..c924d762c 100644
--- a/tests/test_core/test_core_toodt.py
+++ b/tests/test_core/test_core_toodt.py
@@ -683,7 +683,6 @@ def testCoreToOdt_ConvertDirect(mockGUI):
doc = ToOdt(project, isFlat=True)
doc._tokens = [
(doc.T_TEXT, 1, "This is a paragraph", [], doc.A_JUSTIFY),
- (doc.T_EMPTY, 1, "", [], doc.A_NONE),
]
doc.initDocument()
doc.doConvert()
@@ -704,7 +703,6 @@ def testCoreToOdt_ConvertDirect(mockGUI):
doc = ToOdt(project, isFlat=True)
doc._tokens = [
(doc.T_TEXT, 1, "This is a paragraph", [], doc.A_PBA),
- (doc.T_EMPTY, 1, "", [], doc.A_NONE),
]
doc.initDocument()
doc.doConvert()
@@ -1045,10 +1043,26 @@ def testCoreToOdt_ODTParagraphStyle():
assert parStyle._tAttr["font-weight"] == ["fo", None]
parStyle.setFontWeight("normal")
assert parStyle._tAttr["font-weight"] == ["fo", "normal"]
- parStyle.setFontWeight("inherit")
- assert parStyle._tAttr["font-weight"] == ["fo", "inherit"]
parStyle.setFontWeight("bold")
assert parStyle._tAttr["font-weight"] == ["fo", "bold"]
+ parStyle.setFontWeight("100")
+ assert parStyle._tAttr["font-weight"] == ["fo", "100"]
+ parStyle.setFontWeight("200")
+ assert parStyle._tAttr["font-weight"] == ["fo", "200"]
+ parStyle.setFontWeight("300")
+ assert parStyle._tAttr["font-weight"] == ["fo", "300"]
+ parStyle.setFontWeight("400")
+ assert parStyle._tAttr["font-weight"] == ["fo", "400"]
+ parStyle.setFontWeight("500")
+ assert parStyle._tAttr["font-weight"] == ["fo", "500"]
+ parStyle.setFontWeight("600")
+ assert parStyle._tAttr["font-weight"] == ["fo", "600"]
+ parStyle.setFontWeight("700")
+ assert parStyle._tAttr["font-weight"] == ["fo", "700"]
+ parStyle.setFontWeight("800")
+ assert parStyle._tAttr["font-weight"] == ["fo", "800"]
+ parStyle.setFontWeight("900")
+ assert parStyle._tAttr["font-weight"] == ["fo", "900"]
parStyle.setFontWeight("stuff")
assert parStyle._tAttr["font-weight"] == ["fo", None]
@@ -1118,10 +1132,26 @@ def testCoreToOdt_ODTTextStyle():
assert txtStyle._tAttr["font-weight"] == ["fo", None]
txtStyle.setFontWeight("normal")
assert txtStyle._tAttr["font-weight"] == ["fo", "normal"]
- txtStyle.setFontWeight("inherit")
- assert txtStyle._tAttr["font-weight"] == ["fo", "inherit"]
txtStyle.setFontWeight("bold")
assert txtStyle._tAttr["font-weight"] == ["fo", "bold"]
+ txtStyle.setFontWeight("100")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "100"]
+ txtStyle.setFontWeight("200")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "200"]
+ txtStyle.setFontWeight("300")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "300"]
+ txtStyle.setFontWeight("400")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "400"]
+ txtStyle.setFontWeight("500")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "500"]
+ txtStyle.setFontWeight("600")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "600"]
+ txtStyle.setFontWeight("700")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "700"]
+ txtStyle.setFontWeight("800")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "800"]
+ txtStyle.setFontWeight("900")
+ assert txtStyle._tAttr["font-weight"] == ["fo", "900"]
txtStyle.setFontWeight("stuff")
assert txtStyle._tAttr["font-weight"] == ["fo", None]
@@ -1131,10 +1161,10 @@ def testCoreToOdt_ODTTextStyle():
assert txtStyle._tAttr["font-style"] == ["fo", None]
txtStyle.setFontStyle("normal")
assert txtStyle._tAttr["font-style"] == ["fo", "normal"]
- txtStyle.setFontStyle("inherit")
- assert txtStyle._tAttr["font-style"] == ["fo", "inherit"]
txtStyle.setFontStyle("italic")
assert txtStyle._tAttr["font-style"] == ["fo", "italic"]
+ txtStyle.setFontStyle("oblique")
+ assert txtStyle._tAttr["font-style"] == ["fo", "oblique"]
txtStyle.setFontStyle("stuff")
assert txtStyle._tAttr["font-style"] == ["fo", None]
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index 70e950de3..32d6a68b9 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -160,20 +160,12 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, tstPaths):
prefs = GuiPreferences(nwGUI)
prefs.show()
- # Mock Font
- class MockFont:
-
- def family(self):
- return "TestFont"
-
- def pointSize(self):
- return 42
-
# Appearance
prefs.guiLocale.setCurrentIndex(prefs.guiLocale.findData("en_US"))
prefs.guiTheme.setCurrentIndex(prefs.guiTheme.findData("default_dark"))
with monkeypatch.context() as mp:
- mp.setattr(QFontDialog, "getFont", lambda *a: (QFont(), True))
+ mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
+ prefs.nativeFont.setChecked(True) # Use OS font dialog
prefs.guiFontButton.click()
prefs.hideVScroll.setChecked(True)
prefs.hideHScroll.setChecked(True)
@@ -187,7 +179,8 @@ def pointSize(self):
# Document Style
prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
with monkeypatch.context() as mp:
- mp.setattr(QFontDialog, "getFont", lambda *a: (QFont(), True))
+ mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
+ prefs.nativeFont.setChecked(False) # Use Qt font dialog
prefs.textFontButton.click()
prefs.emphLabels.setChecked(False)
prefs.showFullPath.setChecked(False)
diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py
index 1d1ef5918..7d8dd2f35 100644
--- a/tests/test_tools/test_tools_manussettings.py
+++ b/tests/test_tools/test_tools_manussettings.py
@@ -26,7 +26,8 @@
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QFontDialog
-from novelwriter import CONFIG, SHARED
+from novelwriter import SHARED
+from novelwriter.common import describeFont
from novelwriter.constants import nwHeadFmt
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.tools.manussettings import (
@@ -547,11 +548,8 @@ def testBuildSettings_Format(monkeypatch, qtbot, nwGUI):
"""Test the Format Tab of the GuiBuildSettings dialog."""
build = BuildSettings()
- textFont = str(CONFIG.textFont.family())
-
build.setValue("format.buildLang", "en_US")
build.setValue("format.textFont", "") # Will fall back to config value
- build.setValue("format.textSize", 12)
build.setValue("format.lineHeight", 1.2)
build.setValue("format.justifyText", False)
@@ -577,8 +575,6 @@ def testBuildSettings_Format(monkeypatch, qtbot, nwGUI):
assert bSettings.toolStack.currentWidget() is fmtTab
# Check initial values
- assert fmtTab.textFont.text() == textFont
- assert fmtTab.textSize.value() == 12
assert fmtTab.lineHeight.value() == 1.2
assert fmtTab.justifyText.isChecked() is False
@@ -599,8 +595,9 @@ def testBuildSettings_Format(monkeypatch, qtbot, nwGUI):
assert fmtTab.rightMargin.value() == 15.0
# Change values
- fmtTab.textFont.setText("Arial")
- fmtTab.textSize.setValue(11)
+ testFont = QFont("Arial", 11)
+ fmtTab._textFont = testFont
+ fmtTab.textFont.setText(describeFont(testFont))
fmtTab.lineHeight.setValue(1.15)
fmtTab.justifyText.setChecked(True)
@@ -617,8 +614,7 @@ def testBuildSettings_Format(monkeypatch, qtbot, nwGUI):
# Save values
fmtTab.saveContent()
- assert build.getStr("format.textFont") == "Arial"
- assert build.getInt("format.textSize") == 11
+ assert build.getStr("format.textFont") == testFont.toString()
assert build.getFloat("format.lineHeight") == 1.15
assert build.getBool("format.justifyText") is True
@@ -643,11 +639,10 @@ def testBuildSettings_Format(monkeypatch, qtbot, nwGUI):
font = QFont()
font.setFamily("Times")
font.setPointSize(10)
- mp.setattr(QFontDialog, "getFont", lambda *a: (font, True))
+ mp.setattr(QFontDialog, "getFont", lambda *a, **k: (font, True))
fmtTab.btnTextFont.click()
- assert fmtTab.textFont.text() == "Times"
- assert fmtTab.textSize.value() == 10
+ assert fmtTab._textFont == font
# Finish
bSettings._dialogButtonClicked(bSettings.buttonBox.button(QtDialogClose))
@@ -677,13 +672,11 @@ def testBuildSettings_Output(qtbot, nwGUI):
assert outTab.odtPageCountOffset.value() == 0
assert outTab.htmlAddStyles.isChecked() is False
assert outTab.htmlPreserveTabs.isChecked() is False
- assert outTab.mdPreserveBreaks.isChecked() is True
# Toggle all
outTab.odtAddColours.setChecked(True)
outTab.htmlAddStyles.setChecked(True)
outTab.htmlPreserveTabs.setChecked(True)
- outTab.mdPreserveBreaks.setChecked(False)
# Change Values
outTab.odtPageCountOffset.setValue(1)
@@ -697,7 +690,6 @@ def testBuildSettings_Output(qtbot, nwGUI):
assert build.getInt("odt.pageCountOffset") == 1
assert build.getBool("html.addStyles") is True
assert build.getBool("html.preserveTabs") is True
- assert build.getBool("md.preserveBreaks") is False
# Reset header format
outTab.btnPageHeader.click()