Extend heading styling options (#2045)

This commit is contained in:
Veronica Berglyd Olsen
2024-10-15 21:40:21 +02:00
committed by GitHub
25 changed files with 475 additions and 427 deletions
+25 -22
View File
@@ -45,29 +45,30 @@ logger = logging.getLogger(__name__)
# The Settings Template
# =====================
# Each entry contains a tuple on the form:
# (type, default, [min value, max value])
# Each entry contains a tuple on the form: (type, default)
SETTINGS_TEMPLATE = {
SETTINGS_TEMPLATE: dict[str, tuple[type, str | int | float | bool]] = {
"filter.includeNovel": (bool, True),
"filter.includeNotes": (bool, False),
"filter.includeInactive": (bool, False),
"headings.fmtTitle": (str, nwHeadFmt.TITLE),
"headings.fmtPart": (str, nwHeadFmt.TITLE),
"headings.fmtChapter": (str, nwHeadFmt.TITLE),
"headings.fmtUnnumbered": (str, nwHeadFmt.TITLE),
"headings.fmtScene": (str, "* * *"),
"headings.fmtAltScene": (str, ""),
"headings.fmtSection": (str, ""),
"headings.hideTitle": (bool, False),
"headings.hidePart": (bool, False),
"headings.hideChapter": (bool, False),
"headings.hideUnnumbered": (bool, False),
"headings.hideScene": (bool, False),
"headings.hideAltScene": (bool, False),
"headings.hideSection": (bool, True),
"headings.centerTitle": (bool, True),
"headings.centerPart": (bool, True),
"headings.centerChapter": (bool, False),
"headings.centerScene": (bool, False),
"headings.breakTitle": (bool, True),
"headings.breakPart": (bool, True),
"headings.breakChapter": (bool, True),
"headings.breakScene": (bool, False),
"text.includeSynopsis": (bool, False),
@@ -77,7 +78,7 @@ SETTINGS_TEMPLATE = {
"text.ignoredKeywords": (str, ""),
"text.addNoteHeadings": (bool, True),
"format.textFont": (str, CONFIG.textFont.toString()),
"format.lineHeight": (float, 1.15, 0.75, 3.0),
"format.lineHeight": (float, 1.15),
"format.justifyText": (bool, False),
"format.stripUnicode": (bool, False),
"format.replaceTabs": (bool, False),
@@ -94,9 +95,11 @@ SETTINGS_TEMPLATE = {
"format.bottomMargin": (float, 2.0),
"format.leftMargin": (float, 2.0),
"format.rightMargin": (float, 2.0),
"odt.addColours": (bool, True),
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
"odt.pageCountOffset": (int, 0),
"odt.colorHeadings": (bool, True),
"odt.scaleHeadings": (bool, True),
"odt.boldHeadings": (bool, True),
"html.addStyles": (bool, True),
"html.preserveTabs": (bool, False),
}
@@ -108,12 +111,16 @@ SETTINGS_LABELS = {
"filter.includeInactive": QT_TRANSLATE_NOOP("Builds", "Inactive Documents"),
"headings": QT_TRANSLATE_NOOP("Builds", "Headings"),
"headings.fmtTitle": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
"headings.fmtPart": QT_TRANSLATE_NOOP("Builds", "Partition Format"),
"headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"),
"headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"),
"headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"),
"headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"),
"headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"),
"headings.styleTitle": QT_TRANSLATE_NOOP("Builds", "Title Styling"),
"headings.stylePart": QT_TRANSLATE_NOOP("Builds", "Partition Styling"),
"headings.styleChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Styling"),
"headings.styleScene": QT_TRANSLATE_NOOP("Builds", "Scene Styling"),
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
@@ -147,12 +154,14 @@ SETTINGS_LABELS = {
"format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"),
"format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"),
"odt": QT_TRANSLATE_NOOP("Builds", "ODT Documents"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
"odt": QT_TRANSLATE_NOOP("Builds", "Document Options"),
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
"odt.colorHeadings": QT_TRANSLATE_NOOP("Builds", "Add Colours to Headings"),
"odt.scaleHeadings": QT_TRANSLATE_NOOP("Builds", "Increase Size of Headings"),
"odt.boldHeadings": QT_TRANSLATE_NOOP("Builds", "Bold Headings"),
"html": QT_TRANSLATE_NOOP("Builds", "HTML Document"),
"html": QT_TRANSLATE_NOOP("Builds", "HTML Options"),
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
}
@@ -346,18 +355,12 @@ class BuildSettings:
self._changed = True
return
def setValue(self, key: str, value: str | int | bool | float) -> bool:
def setValue(self, key: str, value: str | int | float | bool) -> None:
"""Set a specific value for a build setting."""
if key not in SETTINGS_TEMPLATE:
return False
definition = SETTINGS_TEMPLATE[key]
if not isinstance(value, definition[0]):
return False
if len(definition) == 4 and isinstance(value, (int, float)):
value = min(max(value, definition[2]), definition[3])
self._changed = value != self._settings[key]
self._settings[key] = value
return True
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
self._changed = value != self._settings[key]
self._settings[key] = value
return
##
# Methods
+25 -25
View File
@@ -35,13 +35,13 @@ from novelwriter.constants import nwLabels
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.core.tohtml import ToHtml
from novelwriter.core.tokenizer import Tokenizer
from novelwriter.core.tomarkdown import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.enum import nwBuildFmt
from novelwriter.error import formatException, logException
from novelwriter.formats.tohtml import ToHtml
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tomarkdown import ToMarkdown
from novelwriter.formats.toodt import ToOdt
from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument
logger = logging.getLogger(__name__)
@@ -178,10 +178,7 @@ class NWBuildDocument:
self._cache = makeObj
try:
if isFlat:
makeObj.saveFlatXML(path)
else:
makeObj.saveOpenDocText(path)
makeObj.saveDocument(path)
except Exception as exc:
logException()
self._error = formatException(exc)
@@ -213,10 +210,7 @@ class NWBuildDocument:
if isinstance(path, Path):
try:
if asJson:
makeObj.saveHtmlJson(path)
else:
makeObj.saveHtml5(path)
makeObj.saveDocument(path, asJson=asJson)
except Exception as exc:
logException()
self._error = formatException(exc)
@@ -246,7 +240,7 @@ class NWBuildDocument:
self._cache = makeObj
try:
makeObj.saveMarkdown(path)
makeObj.saveDocument(path)
except Exception as exc:
logException()
self._error = formatException(exc)
@@ -276,10 +270,7 @@ class NWBuildDocument:
if isinstance(path, Path):
try:
if asJson:
makeObj.saveRawMarkdownJSON(path)
else:
makeObj.saveRawMarkdown(path)
makeObj.saveRawDocument(path, asJson=asJson)
except Exception as exc:
logException()
self._error = formatException(exc)
@@ -297,9 +288,9 @@ class NWBuildDocument:
textFont.fromString(self._build.getStr("format.textFont"))
bldObj.setFont(textFont)
bldObj.setTitleFormat(
self._build.getStr("headings.fmtTitle"),
self._build.getBool("headings.hideTitle")
bldObj.setPartitionFormat(
self._build.getStr("headings.fmtPart"),
self._build.getBool("headings.hidePart")
)
bldObj.setChapterFormat(
self._build.getStr("headings.fmtChapter"),
@@ -322,8 +313,12 @@ class NWBuildDocument:
self._build.getBool("headings.hideSection")
)
bldObj.setTitleStyle(
self._build.getBool("headings.centerTitle"),
self._build.getBool("headings.breakTitle")
self._build.getBool("headings.centerPart"),
self._build.getBool("headings.breakPart")
)
bldObj.setPartitionStyle(
self._build.getBool("headings.centerPart"),
self._build.getBool("headings.breakPart")
)
bldObj.setChapterStyle(
self._build.getBool("headings.centerChapter"),
@@ -343,6 +338,11 @@ class NWBuildDocument:
self._build.getFloat("format.firstIndentWidth"),
self._build.getBool("format.indentFirstPar"),
)
bldObj.setHeadingStyles(
self._build.getBool("odt.colorHeadings"),
self._build.getBool("odt.scaleHeadings"),
self._build.getBool("odt.boldHeadings"),
)
bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
@@ -355,10 +355,10 @@ class NWBuildDocument:
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
if isinstance(bldObj, ToOdt):
bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
bldObj.setLanguage(self._project.data.language)
bldObj.setHeaderFormat(
self._build.getStr("odt.pageHeader"), self._build.getInt("odt.pageCountOffset")
self._build.getStr("odt.pageHeader"),
self._build.getInt("odt.pageCountOffset"),
)
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
@@ -32,7 +32,7 @@ from time import time
from novelwriter.common import formatTimeStamp
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.formats.tokenizer import T_Formats, Tokenizer, stripEscape
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__)
@@ -290,51 +290,51 @@ class ToHtml(Tokenizer):
return
def saveHtml5(self, path: str | Path) -> None:
def saveDocument(self, path: str | Path, asJson: bool = False) -> None:
"""Save the data to an HTML file."""
with open(path, mode="w", encoding="utf-8") as fObj:
fObj.write((
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<meta charset='utf-8'>\n"
"<title>{title:s}</title>\n"
"</head>\n"
"<style>\n"
"{style:s}\n"
"</style>\n"
"<body>\n"
"<article>\n"
"{body:s}\n"
"</article>\n"
"</body>\n"
"</html>\n"
).format(
title=self._project.data.name,
style="\n".join(self.getStyleSheet()),
body=("".join(self._fullHTML)).replace("\t", "&#09;").rstrip(),
))
logger.info("Wrote file: %s", path)
return
def saveHtmlJson(self, path: str | Path) -> None:
"""Save the data to a JSON file."""
timeStamp = time()
data = {
"meta": {
"projectName": self._project.data.name,
"novelAuthor": self._project.data.author,
"buildTime": int(timeStamp),
"buildTimeStr": formatTimeStamp(timeStamp),
},
"text": {
"css": self.getStyleSheet(),
"html": [t.replace("\t", "&#09;").rstrip().split("\n") for t in self.fullHTML],
if asJson:
ts = time()
data = {
"meta": {
"projectName": self._project.data.name,
"novelAuthor": self._project.data.author,
"buildTime": int(ts),
"buildTimeStr": formatTimeStamp(ts),
},
"text": {
"css": self.getStyleSheet(),
"html": [t.replace("\t", "&#09;").rstrip().split("\n") for t in self.fullHTML],
}
}
}
with open(path, mode="w", encoding="utf-8") as fObj:
json.dump(data, fObj, indent=2)
with open(path, mode="w", encoding="utf-8") as fObj:
json.dump(data, fObj, indent=2)
else:
with open(path, mode="w", encoding="utf-8") as fObj:
fObj.write((
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<meta charset='utf-8'>\n"
"<title>{title:s}</title>\n"
"</head>\n"
"<style>\n"
"{style:s}\n"
"</style>\n"
"<body>\n"
"<article>\n"
"{body:s}\n"
"</article>\n"
"</body>\n"
"</html>\n"
).format(
title=self._project.data.name,
style="\n".join(self.getStyleSheet()),
body=("".join(self._fullHTML)).replace("\t", "&#09;").rstrip(),
))
logger.info("Wrote file: %s", path)
return
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = "&nbsp;") -> None:
@@ -150,18 +150,21 @@ class Tokenizer(ABC):
# User Settings
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
self._firstWidth = 1.40 # First line indent in units of em
self._indentFirst = False # Indent first paragraph
self._doJustify = False # Justify text
self._doBodyText = True # Include body text
self._doSynopsis = False # Also process synopsis comments
self._doComments = False # Also process comments
self._doKeywords = False # Also process keywords like tags and references
self._skipKeywords = set() # Keywords to ignore
self._keepBreaks = True # Keep line breaks in paragraphs
self._lineHeight = 1.15 # Line height in units of em
self._colorHeads = True # Colourise headings
self._scaleHeads = True # Scale headings to larger font size
self._boldHeads = True # Bold headings
self._blockIndent = 4.00 # Block indent in units of em
self._firstIndent = False # Enable first line indent
self._firstWidth = 1.40 # First line indent in units of em
self._indentFirst = False # Indent first paragraph
self._doJustify = False # Justify text
self._doBodyText = True # Include body text
self._doSynopsis = False # Also process synopsis comments
self._doComments = False # Also process comments
self._doKeywords = False # Also process keywords like tags and references
self._skipKeywords = set() # Keywords to ignore
self._keepBreaks = True # Keep line breaks in paragraphs
# Margins
self._marginTitle = (1.417, 0.500)
@@ -175,14 +178,14 @@ class Tokenizer(ABC):
self._marginSep = (1.168, 1.168)
# Title Formats
self._fmtTitle = nwHeadFmt.TITLE # Formatting for titles
self._fmtPart = nwHeadFmt.TITLE # Formatting for partitions
self._fmtChapter = nwHeadFmt.TITLE # Formatting for numbered chapters
self._fmtUnNum = nwHeadFmt.TITLE # Formatting for unnumbered chapters
self._fmtScene = nwHeadFmt.TITLE # Formatting for scenes
self._fmtHScene = nwHeadFmt.TITLE # Formatting for hard scenes
self._fmtSection = nwHeadFmt.TITLE # Formatting for sections
self._hideTitle = False # Do not include title headings
self._hidePart = False # Do not include partition headings
self._hideChapter = False # Do not include chapter headings
self._hideUnNum = False # Do not include unnumbered headings
self._hideScene = False # Do not include scene headings
@@ -192,6 +195,7 @@ class Tokenizer(ABC):
self._linkHeadings = False # Add an anchor before headings
self._titleStyle = self.A_CENTRE | self.A_PBB
self._partStyle = self.A_CENTRE | self.A_PBB
self._chapterStyle = self.A_PBB
self._sceneStyle = self.A_NONE
@@ -271,10 +275,10 @@ class Tokenizer(ABC):
# Setters
##
def setTitleFormat(self, hFormat: str, hide: bool = False) -> None:
"""Set the title format pattern."""
self._fmtTitle = hFormat.strip()
self._hideTitle = hide
def setPartitionFormat(self, hFormat: str, hide: bool = False) -> None:
"""Set the partition format pattern."""
self._fmtPart = hFormat.strip()
self._hidePart = hide
return
def setChapterFormat(self, hFormat: str, hide: bool = False) -> None:
@@ -314,6 +318,13 @@ class Tokenizer(ABC):
)
return
def setPartitionStyle(self, center: bool, pageBreak: bool) -> None:
"""Set the partition heading style."""
self._partStyle = (
(self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE)
)
return
def setChapterStyle(self, center: bool, pageBreak: bool) -> None:
"""Set the chapter heading style."""
self._chapterStyle = (
@@ -338,6 +349,13 @@ class Tokenizer(ABC):
self._lineHeight = min(max(float(height), 0.5), 5.0)
return
def setHeadingStyles(self, color: bool, scale: bool, bold: bool) -> None:
"""Set text style for headings."""
self._colorHeads = color
self._scaleHeads = scale
self._boldHeads = bold
return
def setBlockIndent(self, indent: float) -> None:
"""Set the block indent between 0.0 and 10.0."""
self._blockIndent = min(max(float(indent), 0.0), 10.0)
@@ -468,6 +486,10 @@ class Tokenizer(ABC):
def doConvert(self) -> None:
raise NotImplementedError
@abstractmethod
def saveDocument(self, path: str | Path) -> None:
raise NotImplementedError
def addRootHeading(self, tHandle: str) -> None:
"""Add a heading at the start of a new root folder."""
self._text = ""
@@ -663,16 +685,16 @@ class Tokenizer(ABC):
nHead += 1
tText = aLine[2:].strip()
tType = self.T_HEAD1 if isPlain else self.T_TITLE
tStyle = self.A_NONE if isPlain else (self.A_PBB | self.A_CENTRE)
sHide = self._hideTitle if isPlain else False
tStyle = self.A_NONE if isPlain else self._titleStyle
sHide = self._hidePart if isPlain else False
if self._isNovel:
if sHide:
tText = ""
tType = self.T_EMPTY
tStyle = self.A_NONE
elif isPlain:
tText = self._hFormatter.apply(self._fmtTitle, tText, nHead)
tStyle = self._titleStyle
tText = self._hFormatter.apply(self._fmtPart, tText, nHead)
tStyle = self._partStyle
if isPlain:
self._hFormatter.resetScene()
else:
@@ -1069,29 +1091,31 @@ class Tokenizer(ABC):
return
def saveRawMarkdown(self, path: str | Path) -> None:
def saveRawDocument(self, path: str | Path, asJson: bool = False) -> None:
"""Save the raw text to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile:
for nwdPage in self._markdown:
outFile.write(nwdPage)
return
def saveRawMarkdownJSON(self, path: str | Path) -> None:
"""Save the raw text to a JSON file."""
timeStamp = time()
data = {
"meta": {
"projectName": self._project.data.name,
"novelAuthor": self._project.data.author,
"buildTime": int(timeStamp),
"buildTimeStr": formatTimeStamp(timeStamp),
},
"text": {
"nwd": [page.rstrip("\n").split("\n") for page in self._markdown],
if asJson:
ts = time()
data = {
"meta": {
"projectName": self._project.data.name,
"novelAuthor": self._project.data.author,
"buildTime": int(ts),
"buildTimeStr": formatTimeStamp(ts),
},
"text": {
"nwd": [page.rstrip("\n").split("\n") for page in self._markdown],
}
}
}
with open(path, mode="w", encoding="utf-8") as fObj:
json.dump(data, fObj, indent=2)
with open(path, mode="w", encoding="utf-8") as fObj:
json.dump(data, fObj, indent=2)
else:
with open(path, mode="w", encoding="utf-8") as outFile:
for nwdPage in self._markdown:
outFile.write(nwdPage)
logger.info("Wrote file: %s", path)
return
##
@@ -29,7 +29,7 @@ from pathlib import Path
from novelwriter.constants import nwHeadFmt, nwLabels, nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import T_Formats, Tokenizer
from novelwriter.formats.tokenizer import T_Formats, Tokenizer
logger = logging.getLogger(__name__)
@@ -199,7 +199,7 @@ class ToMarkdown(Tokenizer):
return
def saveMarkdown(self, path: str | Path) -> None:
def saveDocument(self, path: str | Path) -> None:
"""Save the data to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile:
outFile.write("".join(self._fullMD))
@@ -41,7 +41,7 @@ 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.formats.tokenizer import T_Formats, Tokenizer, stripEscape
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
logger = logging.getLogger(__name__)
@@ -161,7 +161,7 @@ class ToOdt(Tokenizer):
# Properties
self._textFont = QFont("Liberation Serif", 12)
self._colourHead = False
self._headWeight = "bold"
self._headerFormat = ""
self._pageOffset = 0
@@ -224,10 +224,10 @@ class ToOdt(Tokenizer):
self._opaHead12 = None
self._colHead34 = None
self._opaHead34 = None
self._colMetaTx = None
self._opaMetaTx = None
self._colDialogM = None
self._colDialogA = None
self._colMetaTx = "#813709"
self._opaMetaTx = "100%"
self._markText = "#ffffa6"
return
@@ -244,11 +244,6 @@ class ToOdt(Tokenizer):
self._dCountry = country or self._dCountry
return
def setColourHeaders(self, state: bool) -> None:
"""Enable/disable coloured headings and comments."""
self._colourHead = state
return
def setPageLayout(
self, width: int | float, height: int | float,
top: int | float, bottom: int | float, left: int | float, right: int | float
@@ -287,13 +282,15 @@ class ToOdt(Tokenizer):
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._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"
hScale = self._scaleHeads
self._fSizeTitle = f"{round((2.50 if hScale else 1.0) * self._fontSize):d}pt"
self._fSizeHead1 = f"{round((2.00 if hScale else 1.0) * self._fontSize):d}pt"
self._fSizeHead2 = f"{round((1.60 if hScale else 1.0) * self._fontSize):d}pt"
self._fSizeHead3 = f"{round((1.30 if hScale else 1.0) * self._fontSize):d}pt"
self._fSizeHead4 = f"{round((1.15 if hScale else 1.0) * self._fontSize):d}pt"
self._fSizeHead = f"{round((1.15 if hScale else 1.0) * self._fontSize):d}pt"
self._fSizeText = f"{self._fontSize:d}pt"
self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt"
@@ -322,13 +319,11 @@ class ToOdt(Tokenizer):
self._mLeftFoot = self._emToCm(self._marginFoot[0])
self._mBotFoot = self._emToCm(self._marginFoot[1])
if self._colourHead:
if self._colorHeads:
self._colHead12 = "#2a6099"
self._opaHead12 = "100%"
self._colHead34 = "#444444"
self._opaHead34 = "100%"
self._colMetaTx = "#813709"
self._opaMetaTx = "100%"
if self._showDialog:
self._colDialogM = "#2a6099"
@@ -548,46 +543,44 @@ class ToOdt(Tokenizer):
self._xText.insert(0, xFields)
return
def saveFlatXML(self, path: str | Path) -> None:
"""Save the data to an .fodt file."""
with open(path, mode="wb") as fObj:
xml = ET.ElementTree(self._dFlat)
xmlIndent(xml)
xml.write(fObj, encoding="utf-8", xml_declaration=True)
logger.info("Wrote file: %s", path)
return
def saveOpenDocText(self, path: str | Path) -> None:
"""Save the data to an .odt file."""
mMani = _mkTag("manifest", "manifest")
mVers = _mkTag("manifest", "version")
mPath = _mkTag("manifest", "full-path")
mType = _mkTag("manifest", "media-type")
mFile = _mkTag("manifest", "file-entry")
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
oRoot = _mkTag("office", "document-settings")
oVers = _mkTag("office", "version")
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
with zipObj.open(name, mode="w") as fObj:
xml = ET.ElementTree(xObj)
def saveDocument(self, path: str | Path) -> None:
"""Save the data to an .fodt or .odt file."""
if self._isFlat:
with open(path, mode="wb") as fObj:
xml = ET.ElementTree(self._dFlat)
xmlIndent(xml)
xml.write(fObj, encoding="utf-8", xml_declaration=True)
with ZipFile(path, mode="w") as outZip:
outZip.writestr("mimetype", X_MIME)
putInZip("META-INF/manifest.xml", xMani, outZip)
putInZip("settings.xml", xSett, outZip)
putInZip("content.xml", self._dCont, outZip)
putInZip("meta.xml", self._dMeta, outZip)
putInZip("styles.xml", self._dStyl, outZip)
else:
mMani = _mkTag("manifest", "manifest")
mVers = _mkTag("manifest", "version")
mPath = _mkTag("manifest", "full-path")
mType = _mkTag("manifest", "media-type")
mFile = _mkTag("manifest", "file-entry")
xMani = ET.Element(mMani, attrib={mVers: X_VERS})
ET.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
ET.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
ET.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
oRoot = _mkTag("office", "document-settings")
oVers = _mkTag("office", "version")
xSett = ET.Element(oRoot, attrib={oVers: X_VERS})
def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
with zipObj.open(name, mode="w") as fObj:
xml = ET.ElementTree(xObj)
xml.write(fObj, encoding="utf-8", xml_declaration=True)
with ZipFile(path, mode="w") as outZip:
outZip.writestr("mimetype", X_MIME)
putInZip("META-INF/manifest.xml", xMani, outZip)
putInZip("settings.xml", xSett, outZip)
putInZip("content.xml", self._dCont, outZip)
putInZip("meta.xml", self._dMeta, outZip)
putInZip("styles.xml", self._dStyl, outZip)
logger.info("Wrote file: %s", path)
@@ -965,7 +958,7 @@ class ToOdt(Tokenizer):
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeTitle)
style.setFontWeight(self._fontBold)
style.setFontWeight(self._headWeight)
style.packXML(self._xStyl)
self._mainPara[style.name] = style
@@ -998,7 +991,7 @@ class ToOdt(Tokenizer):
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead1)
style.setFontWeight(self._fontBold)
style.setFontWeight(self._headWeight)
style.setColour(self._colHead12)
style.setOpacity(self._opaHead12)
style.packXML(self._xStyl)
@@ -1016,7 +1009,7 @@ class ToOdt(Tokenizer):
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead2)
style.setFontWeight(self._fontBold)
style.setFontWeight(self._headWeight)
style.setColour(self._colHead12)
style.setOpacity(self._opaHead12)
style.packXML(self._xStyl)
@@ -1034,7 +1027,7 @@ class ToOdt(Tokenizer):
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead3)
style.setFontWeight(self._fontBold)
style.setFontWeight(self._headWeight)
style.setColour(self._colHead34)
style.setOpacity(self._opaHead34)
style.packXML(self._xStyl)
@@ -1052,7 +1045,7 @@ class ToOdt(Tokenizer):
style.setFontName(self._fontFamily)
style.setFontFamily(self._fontFamily)
style.setFontSize(self._fSizeHead4)
style.setFontWeight(self._fontBold)
style.setFontWeight(self._headWeight)
style.setColour(self._colHead34)
style.setOpacity(self._opaHead34)
style.packXML(self._xStyl)
@@ -25,6 +25,8 @@ from __future__ import annotations
import logging
from pathlib import Path
from PyQt5.QtGui import (
QColor, QFont, QFontMetricsF, QTextBlockFormat, QTextCharFormat,
QTextCursor, QTextDocument
@@ -32,7 +34,7 @@ from PyQt5.QtGui import (
from novelwriter.constants import nwHeaders, nwHeadFmt, nwKeyWords, nwLabels, nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import T_Formats, Tokenizer
from novelwriter.formats.tokenizer import T_Formats, Tokenizer
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
QtBlack, QtPageBreakAfter, QtPageBreakBefore, QtTransparent,
@@ -114,12 +116,13 @@ class ToQTextDocument(Tokenizer):
self.T_HEAD4: (mPx * self._marginHead4[0], mPx * self._marginHead4[1]),
}
hScale = self._scaleHeads
self._sHead = {
self.T_TITLE: nwHeaders.H_SIZES.get(0, 1.0) * fPt,
self.T_HEAD1: nwHeaders.H_SIZES.get(1, 1.0) * fPt,
self.T_HEAD2: nwHeaders.H_SIZES.get(2, 1.0) * fPt,
self.T_HEAD3: nwHeaders.H_SIZES.get(3, 1.0) * fPt,
self.T_HEAD4: nwHeaders.H_SIZES.get(4, 1.0) * fPt,
self.T_TITLE: (nwHeaders.H_SIZES.get(0, 1.0) * fPt) if hScale else fPt,
self.T_HEAD1: (nwHeaders.H_SIZES.get(1, 1.0) * fPt) if hScale else fPt,
self.T_HEAD2: (nwHeaders.H_SIZES.get(2, 1.0) * fPt) if hScale else fPt,
self.T_HEAD3: (nwHeaders.H_SIZES.get(3, 1.0) * fPt) if hScale else fPt,
self.T_HEAD4: (nwHeaders.H_SIZES.get(4, 1.0) * fPt) if hScale else fPt,
}
self._mText = (mPx * self._marginText[0], mPx * self._marginText[1])
@@ -147,9 +150,6 @@ class ToQTextDocument(Tokenizer):
self._cText.setBackground(QtTransparent)
self._cText.setForeground(self._theme.text)
self._cHead = QTextCharFormat(self._cText)
self._cHead.setForeground(self._theme.head)
self._cComment = QTextCharFormat(self._cText)
self._cComment.setForeground(self._theme.comment)
@@ -275,6 +275,10 @@ class ToQTextDocument(Tokenizer):
return
def saveDocument(self, path: str | Path) -> None:
"""Not implemented."""
return
def appendFootnotes(self) -> None:
"""Append the footnotes in the buffer."""
if self._usedNotes:
@@ -409,8 +413,13 @@ class ToQTextDocument(Tokenizer):
bFmt.setTopMargin(mTop)
bFmt.setBottomMargin(mBottom)
cFmt = QTextCharFormat(self._cText if hType == self.T_TITLE else self._cHead)
cFmt.setFontWeight(self._bold)
self._cTitle = QTextCharFormat(self._cText)
self._cTitle.setFontWeight(self._bold if self._boldHeads else self._normal)
hCol = self._colorHeads and hType != self.T_TITLE
cFmt = QTextCharFormat(self._cText)
cFmt.setForeground(self._theme.head if hCol else self._theme.text)
cFmt.setFontWeight(self._bold if self._boldHeads else self._normal)
cFmt.setFontPointSize(self._sHead.get(hType, 1.0))
if nHead >= 0:
cFmt.setAnchorNames([f"{self._handle}:T{nHead:04d}"])
+1 -1
View File
@@ -39,12 +39,12 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwHeaders, nwUnicode
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.enum import nwDocAction, nwDocMode, nwItemType
from novelwriter.error import logException
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.eventfilters import WheelEventFilter
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
from novelwriter.types import (
QtAlignCenterTop, QtKeepAnchor, QtMouseLeft, QtMoveAnchor,
+3 -3
View File
@@ -42,10 +42,10 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import fuzzyTime
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.tokenizer import HeadingFormatter
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog
from novelwriter.extensions.progressbars import NProgressCircle
from novelwriter.formats.tokenizer import HeadingFormatter
from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
from novelwriter.tools.manusbuild import GuiManuscriptBuild
from novelwriter.tools.manussettings import GuiBuildSettings
@@ -593,7 +593,7 @@ class _DetailsWidget(QWidget):
item.setText(1, "")
self.listView.addTopLevelItem(item)
for hFormat, hHide in [
("headings.fmtTitle", "headings.hideTitle"),
("headings.fmtPart", "headings.hidePart"),
("headings.fmtChapter", "headings.hideChapter"),
("headings.fmtUnnumbered", "headings.hideUnnumbered"),
("headings.fmtScene", "headings.hideScene"),
+68 -61
View File
@@ -50,8 +50,8 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.switchbox import NSwitchBox
from novelwriter.types import (
QtAlignLeft, QtDialogApply, QtDialogClose, QtDialogSave, QtRoleAccept,
QtRoleApply, QtRoleReject, QtUserRole
QtAlignCenter, QtAlignLeft, QtDialogApply, QtDialogClose, QtDialogSave,
QtRoleAccept, QtRoleApply, QtRoleReject, QtUserRole
)
if TYPE_CHECKING: # pragma: no cover
@@ -585,20 +585,20 @@ class _HeadingsTab(NScrollablePage):
self.formatBox.setHorizontalSpacing(bSp)
# Title Heading
self.lblTitle = QLabel(self._build.getLabel("headings.fmtTitle"), self)
self.fmtTitle = QLineEdit("", self)
self.fmtTitle.setReadOnly(True)
self.btnTitle = NIconToolButton(self, iSz, "edit")
self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
self.hdeTitle = QLabel(trHide, self)
self.hdeTitle.setIndent(bSp)
self.swtTitle = NSwitch(self, height=iPx)
self.lblPart = QLabel(self._build.getLabel("headings.fmtPart"), self)
self.fmtPart = QLineEdit("", self)
self.fmtPart.setReadOnly(True)
self.btnPart = NIconToolButton(self, iSz, "edit")
self.btnPart.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE))
self.hdePart = QLabel(trHide, self)
self.hdePart.setIndent(bSp)
self.swtPart = NSwitch(self, height=iPx)
self.formatBox.addWidget(self.lblTitle, 0, 0)
self.formatBox.addWidget(self.fmtTitle, 0, 1)
self.formatBox.addWidget(self.btnTitle, 0, 2)
self.formatBox.addWidget(self.hdeTitle, 0, 3)
self.formatBox.addWidget(self.swtTitle, 0, 4)
self.formatBox.addWidget(self.lblPart, 0, 0)
self.formatBox.addWidget(self.fmtPart, 0, 1)
self.formatBox.addWidget(self.btnPart, 0, 2)
self.formatBox.addWidget(self.hdePart, 0, 3)
self.formatBox.addWidget(self.swtPart, 0, 4)
# Chapter Heading
self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter"), self)
@@ -734,56 +734,46 @@ class _HeadingsTab(NScrollablePage):
self.layoutMatrix.setVerticalSpacing(vSp)
self.layoutMatrix.setHorizontalSpacing(vSp)
# Heading
self.layoutHeading = QLabel("<b>{0}</b>".format(self.tr("Additional Styling")), self)
self.layoutMatrix.addWidget(self.layoutHeading, 0, 0, 1, 5)
self.layoutMatrix.addWidget(QLabel(self.tr("Centre"), self), 0, 1)
self.layoutMatrix.addWidget(QLabel(self.tr("Page Break"), self), 0, 2)
# Title Layout
self.mtxTitle = QLabel(self._build.getLabel("headings.fmtTitle"), self)
self.lblTitle = QLabel(self._build.getLabel("headings.styleTitle"), self)
self.centerTitle = NSwitch(self, height=iPx)
self.breakTitle = NSwitch(self, height=iPx)
lblCenterT = QLabel(self.tr("Centre"), self)
lblCenterT.setIndent(sSp)
lblBreakT = QLabel(self.tr("Page Break"), self)
lblBreakT.setIndent(sSp)
self.layoutMatrix.addWidget(self.mtxTitle, 1, 0)
self.layoutMatrix.addWidget(lblCenterT, 1, 1)
self.layoutMatrix.addWidget(self.centerTitle, 1, 2)
self.layoutMatrix.addWidget(lblBreakT, 1, 3)
self.layoutMatrix.addWidget(self.breakTitle, 1, 4)
self.layoutMatrix.addWidget(self.lblTitle, 1, 0)
self.layoutMatrix.addWidget(self.centerTitle, 1, 1, QtAlignCenter)
self.layoutMatrix.addWidget(self.breakTitle, 1, 2, QtAlignCenter)
# Partition Layout
self.lblPart = QLabel(self._build.getLabel("headings.stylePart"), self)
self.centerPart = NSwitch(self, height=iPx)
self.breakPart = NSwitch(self, height=iPx)
self.layoutMatrix.addWidget(self.lblPart, 2, 0)
self.layoutMatrix.addWidget(self.centerPart, 2, 1, QtAlignCenter)
self.layoutMatrix.addWidget(self.breakPart, 2, 2, QtAlignCenter)
# Chapter Layout
self.mtxChapter = QLabel(self._build.getLabel("headings.fmtChapter"), self)
self.lblChapter = QLabel(self._build.getLabel("headings.styleChapter"), self)
self.centerChapter = NSwitch(self, height=iPx)
self.breakChapter = NSwitch(self, height=iPx)
lblCenterC = QLabel(self.tr("Centre"), self)
lblCenterC.setIndent(sSp)
lblBreakC = QLabel(self.tr("Page Break"), self)
lblBreakC.setIndent(sSp)
self.layoutMatrix.addWidget(self.mtxChapter, 2, 0)
self.layoutMatrix.addWidget(lblCenterC, 2, 1)
self.layoutMatrix.addWidget(self.centerChapter, 2, 2)
self.layoutMatrix.addWidget(lblBreakC, 2, 3)
self.layoutMatrix.addWidget(self.breakChapter, 2, 4)
self.layoutMatrix.addWidget(self.lblChapter, 3, 0)
self.layoutMatrix.addWidget(self.centerChapter, 3, 1, QtAlignCenter)
self.layoutMatrix.addWidget(self.breakChapter, 3, 2, QtAlignCenter)
# Scene Layout
self.mtxScene = QLabel(self._build.getLabel("headings.fmtScene"), self)
self.lblScene = QLabel(self._build.getLabel("headings.styleScene"), self)
self.centerScene = NSwitch(self, height=iPx)
self.breakScene = NSwitch(self, height=iPx)
lblCenterS = QLabel(self.tr("Centre"), self)
lblCenterS.setIndent(sSp)
lblBreakS = QLabel(self.tr("Page Break"), self)
lblBreakS.setIndent(sSp)
self.layoutMatrix.addWidget(self.mtxScene, 3, 0)
self.layoutMatrix.addWidget(lblCenterS, 3, 1)
self.layoutMatrix.addWidget(self.centerScene, 3, 2)
self.layoutMatrix.addWidget(lblBreakS, 3, 3)
self.layoutMatrix.addWidget(self.breakScene, 3, 4)
self.layoutMatrix.addWidget(self.lblScene, 4, 0)
self.layoutMatrix.addWidget(self.centerScene, 4, 1, QtAlignCenter)
self.layoutMatrix.addWidget(self.breakScene, 4, 2, QtAlignCenter)
self.layoutMatrix.setColumnStretch(5, 1)
self.layoutMatrix.setColumnStretch(3, 1)
# Assemble
# ========
@@ -802,14 +792,14 @@ class _HeadingsTab(NScrollablePage):
def loadContent(self) -> None:
"""Populate the widgets."""
self.fmtTitle.setText(self._build.getStr("headings.fmtTitle"))
self.fmtPart.setText(self._build.getStr("headings.fmtPart"))
self.fmtChapter.setText(self._build.getStr("headings.fmtChapter"))
self.fmtUnnumbered.setText(self._build.getStr("headings.fmtUnnumbered"))
self.fmtScene.setText(self._build.getStr("headings.fmtScene"))
self.fmtAScene.setText(self._build.getStr("headings.fmtAltScene"))
self.fmtSection.setText(self._build.getStr("headings.fmtSection"))
self.swtTitle.setChecked(self._build.getBool("headings.hideTitle"))
self.swtPart.setChecked(self._build.getBool("headings.hidePart"))
self.swtChapter.setChecked(self._build.getBool("headings.hideChapter"))
self.swtUnnumbered.setChecked(self._build.getBool("headings.hideUnnumbered"))
self.swtScene.setChecked(self._build.getBool("headings.hideScene"))
@@ -817,16 +807,19 @@ class _HeadingsTab(NScrollablePage):
self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
self.centerTitle.setChecked(self._build.getBool("headings.centerTitle"))
self.centerPart.setChecked(self._build.getBool("headings.centerPart"))
self.centerChapter.setChecked(self._build.getBool("headings.centerChapter"))
self.centerScene.setChecked(self._build.getBool("headings.centerScene"))
self.breakTitle.setChecked(self._build.getBool("headings.breakTitle"))
self.breakPart.setChecked(self._build.getBool("headings.breakPart"))
self.breakChapter.setChecked(self._build.getBool("headings.breakChapter"))
self.breakScene.setChecked(self._build.getBool("headings.breakScene"))
return
def saveContent(self) -> None:
"""Save choices back into build object."""
self._build.setValue("headings.hideTitle", self.swtTitle.isChecked())
self._build.setValue("headings.hidePart", self.swtPart.isChecked())
self._build.setValue("headings.hideChapter", self.swtChapter.isChecked())
self._build.setValue("headings.hideUnnumbered", self.swtUnnumbered.isChecked())
self._build.setValue("headings.hideScene", self.swtScene.isChecked())
@@ -834,9 +827,12 @@ class _HeadingsTab(NScrollablePage):
self._build.setValue("headings.hideSection", self.swtSection.isChecked())
self._build.setValue("headings.centerTitle", self.centerTitle.isChecked())
self._build.setValue("headings.centerPart", self.centerPart.isChecked())
self._build.setValue("headings.centerChapter", self.centerChapter.isChecked())
self._build.setValue("headings.centerScene", self.centerScene.isChecked())
self._build.setValue("headings.breakTitle", self.breakTitle.isChecked())
self._build.setValue("headings.breakPart", self.breakPart.isChecked())
self._build.setValue("headings.breakChapter", self.breakChapter.isChecked())
self._build.setValue("headings.breakScene", self.breakScene.isChecked())
return
@@ -858,8 +854,8 @@ class _HeadingsTab(NScrollablePage):
self._editing = heading
self.editTextBox.setEnabled(True)
if heading == self.EDIT_TITLE:
text = self.fmtTitle.text()
label = self._build.getLabel("headings.fmtTitle")
text = self.fmtPart.text()
label = self._build.getLabel("headings.fmtPart")
elif heading == self.EDIT_CHAPTER:
text = self.fmtChapter.text()
label = self._build.getLabel("headings.fmtChapter")
@@ -896,8 +892,8 @@ class _HeadingsTab(NScrollablePage):
heading = self._editing
text = self.editTextBox.toPlainText().strip().replace("\n", nwHeadFmt.BR)
if heading == self.EDIT_TITLE:
self.fmtTitle.setText(text)
self._build.setValue("headings.fmtTitle", text)
self.fmtPart.setText(text)
self._build.setValue("headings.fmtPart", text)
elif heading == self.EDIT_CHAPTER:
self.fmtChapter.setText(text)
self._build.setValue("headings.fmtChapter", text)
@@ -1126,9 +1122,7 @@ class _FormattingTab(NScrollableForm):
self._sidebar.addButton(title, section)
self.addGroupLabel(title, section)
self.odtAddColours = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("odt.addColours"), self.odtAddColours)
# Header
self.odtPageHeader = QLineEdit(self)
self.odtPageHeader.setMinimumWidth(CONFIG.pxInt(200))
self.btnPageHeader = NIconToolButton(self, iSz, "revert")
@@ -1145,6 +1139,15 @@ class _FormattingTab(NScrollableForm):
self.odtPageCountOffset.setMinimumWidth(spW)
self.addRow(self._build.getLabel("odt.pageCountOffset"), self.odtPageCountOffset)
# Headings
self.colorHeadings = NSwitch(self, height=iPx)
self.scaleHeadings = NSwitch(self, height=iPx)
self.boldHeadings = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("odt.colorHeadings"), self.colorHeadings)
self.addRow(self._build.getLabel("odt.scaleHeadings"), self.scaleHeadings)
self.addRow(self._build.getLabel("odt.boldHeadings"), self.boldHeadings)
# HTML Document
# =============
@@ -1229,7 +1232,9 @@ class _FormattingTab(NScrollableForm):
# ODT Document
# ============
self.odtAddColours.setChecked(self._build.getBool("odt.addColours"))
self.colorHeadings.setChecked(self._build.getBool("odt.colorHeadings"))
self.scaleHeadings.setChecked(self._build.getBool("odt.scaleHeadings"))
self.boldHeadings.setChecked(self._build.getBool("odt.boldHeadings"))
self.odtPageHeader.setText(self._build.getStr("odt.pageHeader"))
self.odtPageCountOffset.setValue(self._build.getInt("odt.pageCountOffset"))
self.odtPageHeader.setCursorPosition(0)
@@ -1279,7 +1284,9 @@ class _FormattingTab(NScrollableForm):
self._build.setValue("format.rightMargin", self.rightMargin.value())
# ODT Document
self._build.setValue("odt.addColours", self.odtAddColours.isChecked())
self._build.setValue("odt.colorHeadings", self.colorHeadings.isChecked())
self._build.setValue("odt.scaleHeadings", self.scaleHeadings.isChecked())
self._build.setValue("odt.boldHeadings", self.boldHeadings.isChecked())
self._build.setValue("odt.pageHeader", self.odtPageHeader.text())
self._build.setValue("odt.pageCountOffset", self.odtPageCountOffset.value())
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<office:document-styles xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3">
<office:document-styles xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3">
<office:font-face-decls>
<style:font-face style:name="Liberation Serif" style:font-pitch="variable" />
</office:font-face-decls>
@@ -25,7 +25,7 @@
</style:style>
<style:style style:name="Text_20_Meta" style:family="paragraph" style:display-name="Text Meta" style:parent-style-name="Standard" style:class="text">
<style:paragraph-properties fo:margin-top="0.000cm" fo:margin-bottom="0.247cm" fo:line-height="115%" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="12pt" fo:font-weight="normal" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="12pt" fo:font-weight="normal" fo:color="#813709" loext:opacity="100%" />
</style:style>
<style:style style:name="Title" style:family="paragraph" style:display-name="Title" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.600cm" fo:margin-bottom="0.212cm" fo:text-align="center" />
@@ -37,19 +37,19 @@
</style:style>
<style:style style:name="Heading_20_1" style:family="paragraph" style:display-name="Heading 1" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="text">
<style:paragraph-properties fo:margin-top="0.600cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="24pt" fo:font-weight="bold" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="24pt" fo:font-weight="bold" fo:color="#2a6099" loext:opacity="100%" />
</style:style>
<style:style style:name="Heading_20_2" style:family="paragraph" style:display-name="Heading 2" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="text">
<style:paragraph-properties fo:margin-top="0.706cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="19pt" fo:font-weight="bold" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="19pt" fo:font-weight="bold" fo:color="#2a6099" loext:opacity="100%" />
</style:style>
<style:style style:name="Heading_20_3" style:family="paragraph" style:display-name="Heading 3" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="text">
<style:paragraph-properties fo:margin-top="0.494cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="16pt" fo:font-weight="bold" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="16pt" fo:font-weight="bold" fo:color="#444444" loext:opacity="100%" />
</style:style>
<style:style style:name="Heading_20_4" style:family="paragraph" style:display-name="Heading 4" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="4" style:class="text">
<style:paragraph-properties fo:margin-top="0.494cm" fo:margin-bottom="0.212cm" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="14pt" fo:font-weight="bold" />
<style:text-properties style:font-name="Liberation Serif" fo:font-family="Liberation Serif" fo:font-size="14pt" fo:font-weight="bold" fo:color="#444444" loext:opacity="100%" />
</style:style>
<style:style style:name="Header" style:family="paragraph" style:display-name="Header" style:parent-style-name="Header_20_and_20_Footer">
<style:paragraph-properties fo:text-align="right" />
@@ -18,7 +18,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
_Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Title: Act One
# Part: Act One
“Fusce maximus felis libero”
@@ -34,7 +34,7 @@ mark {background: rgb(255, 255, 166);}
<h1 style='page-break-before: always;'>Prologue</h1>
<p class='synopsis'><strong>Synopsis:</strong> Explanation from the lipsum.com website.</p>
<p><em>Lorem Ipsum</em> is simply dummy text<sup><a href='#footnote_1'>1</a></sup> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<h1 class='title' style='text-align: center; page-break-before: always;'>Title: Act One</h1>
<h1 class='title' style='text-align: center; page-break-before: always;'>Part: Act One</h1>
<p style='text-align: center;'>“Fusce maximus felis libero”</p>
<h1 style='page-break-before: always;'>Chapter: Chapter One</h1>
<p class='meta meta-pov' style='margin-bottom: 0;'><span class='keyword'>Point of View:</span> <a class='tag' href='#tag_Bod'>Bod</a></p>
@@ -2,8 +2,8 @@
"meta": {
"projectName": "Lorem Ipsum",
"novelAuthor": "lipsum.com",
"buildTime": 1718057434,
"buildTimeStr": "2024-06-11 00:10:34"
"buildTime": 1727777646,
"buildTimeStr": "2024-10-01 12:14:06"
},
"text": {
"css": [
@@ -42,7 +42,7 @@
"<p><em>Lorem Ipsum</em> is simply dummy text<sup><a href='#footnote_1'>1</a></sup> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>"
],
[
"<h1 class='title' style='text-align: center; page-break-before: always;'>Title: Act One</h1>",
"<h1 class='title' style='text-align: center; page-break-before: always;'>Part: Act One</h1>",
"<p style='text-align: center;'>\u201cFusce maximus felis libero\u201d</p>"
],
[
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<office:document xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta>
<meta:creation-date>2024-09-30T14:08:16</meta:creation-date>
<meta:creation-date>2024-10-01T12:11:50</meta:creation-date>
<meta:generator>novelWriter/2.6a0</meta:generator>
<meta:initial-creator>lipsum.com</meta:initial-creator>
<meta:editing-cycles>45</meta:editing-cycles>
<meta:editing-duration>P0DT0H36M8S</meta:editing-duration>
<dc:title>Lorem Ipsum</dc:title>
<dc:date>2024-09-30T14:08:16</dc:date>
<dc:date>2024-10-01T12:11:50</dc:date>
<dc:creator>lipsum.com</dc:creator>
</office:meta>
<office:font-face-decls>
@@ -122,12 +122,12 @@
<text:user-field-decl office:value-type="float" office:value="4163" text:name="ManuscriptAllWords" />
<text:user-field-decl office:value-type="float" office:value="3809" text:name="ManuscriptTextWords" />
<text:user-field-decl office:value-type="float" office:value="54" text:name="ManuscriptTitleWords" />
<text:user-field-decl office:value-type="float" office:value="27849" text:name="ManuscriptAllChars" />
<text:user-field-decl office:value-type="float" office:value="27848" text:name="ManuscriptAllChars" />
<text:user-field-decl office:value-type="float" office:value="25528" text:name="ManuscriptTextChars" />
<text:user-field-decl office:value-type="float" office:value="311" text:name="ManuscriptTitleChars" />
<text:user-field-decl office:value-type="float" office:value="23782" text:name="ManuscriptAllWordChars" />
<text:user-field-decl office:value-type="float" office:value="310" text:name="ManuscriptTitleChars" />
<text:user-field-decl office:value-type="float" office:value="23781" text:name="ManuscriptAllWordChars" />
<text:user-field-decl office:value-type="float" office:value="21761" text:name="ManuscriptTextWordChars" />
<text:user-field-decl office:value-type="float" office:value="276" text:name="ManuscriptTitleWordChars" />
<text:user-field-decl office:value-type="float" office:value="275" text:name="ManuscriptTitleWordChars" />
</text:user-field-decls>
<text:p text:style-name="Title">Lorem Ipsum</text:p>
<text:p text:style-name="P1"><text:span text:style-name="T1">By lipsum.com</text:span></text:p>
@@ -144,7 +144,7 @@
<text:p text:style-name="Footnote"><text:span text:style-name="T2">Lorem ipsum</text:span> is typically a corrupted version of De finibus bonorum et malorum, a 1st-century BC text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical and improper Latin. (Source: Wikipedia)</text:p>
</text:note-body>
</text:note> of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</text:p>
<text:h text:style-name="P4" text:outline-level="1">Title: Act One</text:h>
<text:h text:style-name="P4" text:outline-level="1">Part: Act One</text:h>
<text:p text:style-name="P1">“Fusce maximus felis libero”</text:p>
<text:h text:style-name="P3" text:outline-level="2">Chapter: Chapter One</text:h>
<text:p text:style-name="P5"><text:span text:style-name="T1">Point of View:</text:span> Bod</text:p>
@@ -18,7 +18,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
_Lorem Ipsum_ is simply dummy text[1] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
# Title: Act One
# Part: Act One
“Fusce maximus felis libero”
+17 -18
View File
@@ -29,7 +29,7 @@ from pathlib import Path
import pytest
from novelwriter import CONFIG
from novelwriter.constants import nwFiles
from novelwriter.constants import nwFiles, nwHeadFmt
from novelwriter.core.buildsettings import BuildCollection, BuildSettings, FilterMode
from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt, nwItemClass
@@ -147,49 +147,48 @@ def testCoreBuildSettings_BuildValues():
"""Test BuildSettings get/set of build values."""
build = BuildSettings()
strSetting = "headings.fmtTitle"
strSetting = "headings.fmtPart"
intSetting = "odt.pageCountOffset"
boolSetting = "filter.includeNovel"
floatSetting = "format.lineHeight"
# Invalid setting
assert build.setValue("foo", "bar") is False
build.setValue("foo", "bar")
assert build.getStr("foo") == "None"
# Value must be correct type
assert build.setValue(strSetting, 15) is False
assert build.setValue(intSetting, 15.0) is False
assert build.setValue(boolSetting, "string") is False
assert build.setValue(floatSetting, 15) is False
# Check min/max range
assert build.setValue(floatSetting, 200.0) is True
assert build.getFloat(floatSetting) == 3.0
assert build.setValue(floatSetting, 0.0) is True
assert build.getFloat(floatSetting) == 0.75
build.setValue(strSetting, 15)
assert build.getStr(strSetting) == nwHeadFmt.TITLE
build.setValue(intSetting, 15.0)
assert build.getInt(intSetting) == 0
build.setValue(floatSetting, 15)
assert build.getFloat(floatSetting) == 1.15
build.setValue(boolSetting, "string")
assert build.getBool(boolSetting) is True
# Check string values
assert build.setValue(strSetting, "foobar") is True
build.setValue(strSetting, "foobar")
assert build.getStr(strSetting) == "foobar"
assert build.getInt(strSetting) == 0
assert build.getBool(strSetting) is True
assert build.getFloat(strSetting) == 0.0
# Check int values
assert build.setValue(intSetting, 42) is True
build.setValue(intSetting, 42)
assert build.getStr(intSetting) == "42"
assert build.getInt(intSetting) == 42
assert build.getBool(intSetting) is True
assert build.getFloat(intSetting) == 42.0
# Check bool values
assert build.setValue(boolSetting, True) is True
build.setValue(boolSetting, True)
assert build.getStr(boolSetting) == "True"
assert build.getInt(boolSetting) == 1
assert build.getBool(boolSetting) is True
assert build.getFloat(boolSetting) == 1.0
# Check float values
assert build.setValue(floatSetting, 2.5) is True
build.setValue(floatSetting, 2.5)
assert build.getStr(floatSetting) == "2.5"
assert build.getInt(floatSetting) == 2
assert build.getBool(floatSetting) is True
@@ -334,7 +333,7 @@ def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd):
hCharDoc: (True, FilterMode.INCLUDED),
}
# Set everything back to filered
# Set everything back to filtered
build.setValue("filter.includeNotes", False)
build.setAllowRoot(C.hPlotRoot, False)
build.setAllowRoot(C.hCharRoot, True)
+6 -6
View File
@@ -30,10 +30,10 @@ import pytest
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.project import NWProject
from novelwriter.core.tohtml import ToHtml
from novelwriter.core.tomarkdown import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.enum import nwBuildFmt
from novelwriter.formats.tohtml import ToHtml
from novelwriter.formats.tomarkdown import ToMarkdown
from novelwriter.formats.toodt import ToOdt
from tests.mocked import causeException, causeOSError
from tests.tools import ODT_IGNORE, C, buildTestProject, cmpFiles
@@ -45,7 +45,7 @@ BUILD_CONF = {
"filter.includeNovel": True,
"filter.includeNotes": True,
"filter.includeInactive": True,
"headings.fmtTitle": "Title: {Title}",
"headings.fmtPart": "Part: {Title}",
"headings.fmtChapter": "Chapter: {Title}",
"headings.fmtUnnumbered": "{Title}",
"headings.fmtScene": "Scene: {Title}",
@@ -64,7 +64,7 @@ BUILD_CONF = {
"format.stripUnicode": False,
"format.replaceTabs": True,
"format.firstLineIndent": True,
"odt.addColours": True,
"odt.colorHeadings": True,
"html.addStyles": True,
},
"content": {
@@ -146,7 +146,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
# ==================
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.toodt.ToOdt.doConvert", causeException)
mp.setattr("novelwriter.formats.toodt.ToOdt.doConvert", causeException)
assert len(docBuild) == 21
count = 0
@@ -26,11 +26,11 @@ import pytest
from novelwriter import CONFIG
from novelwriter.core.project import NWProject
from novelwriter.core.tohtml import ToHtml
from novelwriter.formats.tohtml import ToHtml
@pytest.mark.core
def testCoreToHtml_ConvertHeaders(mockGUI):
def testFmtToHtml_ConvertHeaders(mockGUI):
"""Test header formats in the ToHtml class."""
project = NWProject()
html = ToHtml(project)
@@ -132,7 +132,7 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
@pytest.mark.core
def testCoreToHtml_ConvertParagraphs(mockGUI):
def testFmtToHtml_ConvertParagraphs(mockGUI):
"""Test paragraph formats in the ToHtml class."""
project = NWProject()
html = ToHtml(project)
@@ -309,7 +309,7 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
@pytest.mark.core
def testCoreToHtml_CloseTags(mockGUI):
def testFmtToHtml_CloseTags(mockGUI):
"""Test automatic closing of HTML tags for shortcodes."""
project = NWProject()
html = ToHtml(project)
@@ -345,7 +345,7 @@ def testCoreToHtml_CloseTags(mockGUI):
@pytest.mark.core
def testCoreToHtml_ConvertDirect(mockGUI):
def testFmtToHtml_ConvertDirect(mockGUI):
"""Test the converter directly using the ToHtml class."""
project = NWProject()
html = ToHtml(project)
@@ -492,7 +492,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
@pytest.mark.core
def testCoreToHtml_SpecialCases(mockGUI):
def testFmtToHtml_SpecialCases(mockGUI):
"""Test some special cases that have caused errors in the past."""
project = NWProject()
html = ToHtml(project)
@@ -564,7 +564,7 @@ def testCoreToHtml_SpecialCases(mockGUI):
@pytest.mark.core
def testCoreToHtml_Save(mockGUI, fncPath):
def testFmtToHtml_Save(mockGUI, fncPath):
"""Test the save method of the ToHtml class."""
project = NWProject()
html = ToHtml(project)
@@ -644,12 +644,12 @@ def testCoreToHtml_Save(mockGUI, fncPath):
)
saveFile = fncPath / "outFile.htm"
html.saveHtml5(saveFile)
html.saveDocument(saveFile, asJson=False)
assert saveFile.read_text(encoding="utf-8") == htmlDoc
# JSON + HTML
saveFile = fncPath / "outFile.json"
html.saveHtmlJson(saveFile)
html.saveDocument(saveFile, asJson=True)
data = json.loads(saveFile.read_text(encoding="utf-8"))
assert data["meta"]["projectName"] == ""
assert data["meta"]["novelAuthor"] == ""
@@ -660,7 +660,7 @@ def testCoreToHtml_Save(mockGUI, fncPath):
@pytest.mark.core
def testCoreToHtml_Methods(mockGUI):
def testFmtToHtml_Methods(mockGUI):
"""Test all the other methods of the ToHtml class."""
project = NWProject()
html = ToHtml(project)
@@ -707,7 +707,7 @@ def testCoreToHtml_Methods(mockGUI):
@pytest.mark.core
def testCoreToHtml_Format(mockGUI):
def testFmtToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class."""
project = NWProject()
html = ToHtml(project)
@@ -29,8 +29,8 @@ from PyQt5.QtGui import QFont
from novelwriter import CONFIG
from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape
from novelwriter.core.tomarkdown import ToMarkdown
from novelwriter.formats.tokenizer import HeadingFormatter, Tokenizer, stripEscape
from novelwriter.formats.tomarkdown import ToMarkdown
from tests.tools import C, buildTestProject, readFile
@@ -39,15 +39,18 @@ class BareTokenizer(Tokenizer):
def doConvert(self):
super().doConvert() # type: ignore (deliberate check)
def saveDocument(self, path) -> None:
super().saveDocument(path) # type: ignore (deliberate check)
@pytest.mark.core
def testCoreToken_Setters(mockGUI):
def testFmtToken_Setters(mockGUI):
"""Test all the setters for the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
# Verify defaults
assert tokens._fmtTitle == nwHeadFmt.TITLE
assert tokens._fmtPart == nwHeadFmt.TITLE
assert tokens._fmtChapter == nwHeadFmt.TITLE
assert tokens._fmtUnNum == nwHeadFmt.TITLE
assert tokens._fmtScene == nwHeadFmt.TITLE
@@ -65,7 +68,7 @@ def testCoreToken_Setters(mockGUI):
assert tokens._marginText == (0.000, 0.584)
assert tokens._marginMeta == (0.000, 0.584)
assert tokens._marginSep == (1.168, 1.168)
assert tokens._hideTitle is False
assert tokens._hidePart is False
assert tokens._hideChapter is False
assert tokens._hideUnNum is False
assert tokens._hideScene is False
@@ -78,7 +81,7 @@ def testCoreToken_Setters(mockGUI):
assert tokens._doKeywords is False
# Set new values
tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}", True)
tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}", True)
tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}", True)
tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}", True)
tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True)
@@ -103,7 +106,7 @@ def testCoreToken_Setters(mockGUI):
tokens.setKeywords(True)
# Check new values
assert tokens._fmtTitle == f"T: {nwHeadFmt.TITLE}"
assert tokens._fmtPart == f"T: {nwHeadFmt.TITLE}"
assert tokens._fmtChapter == f"C: {nwHeadFmt.TITLE}"
assert tokens._fmtUnNum == f"U: {nwHeadFmt.TITLE}"
assert tokens._fmtScene == f"S: {nwHeadFmt.TITLE}"
@@ -121,7 +124,7 @@ def testCoreToken_Setters(mockGUI):
assert tokens._marginText == (2.0, 2.0)
assert tokens._marginMeta == (2.0, 2.0)
assert tokens._marginSep == (2.0, 2.0)
assert tokens._hideTitle is True
assert tokens._hidePart is True
assert tokens._hideChapter is True
assert tokens._hideUnNum is True
assert tokens._hideScene is True
@@ -152,7 +155,7 @@ def testCoreToken_Setters(mockGUI):
@pytest.mark.core
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
def testFmtToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
"""Test handling files and text in the Tokenizer class."""
project = NWProject()
mockRnd.reset()
@@ -219,12 +222,12 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
# Save File
savePath = fncPath / "dump.nwd"
tokens.saveRawMarkdown(savePath)
tokens.saveRawDocument(savePath, asJson=False)
assert readFile(savePath) == (
"#! Notes: Plot\n\n"
"#! Notes: Plot\n\n"
)
tokens.saveRawMarkdownJSON(savePath)
tokens.saveRawDocument(savePath, asJson=True)
assert json.loads(readFile(savePath))["text"] == {
"nwd": [
["#! Notes: Plot"],
@@ -232,13 +235,16 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
]
}
# Check abstract method
# Check abstract methods
with pytest.raises(NotImplementedError):
tokens.doConvert()
with pytest.raises(NotImplementedError):
tokens.saveDocument(fncPath)
@pytest.mark.core
def testCoreToken_StripEscape():
def testFmtToken_StripEscape():
"""Test the stripEscape helper function."""
text1 = "This is text with escapes: \\** \\~~ \\__"
text2 = "This is text with escapes: ** ~~ __"
@@ -247,7 +253,7 @@ def testCoreToken_StripEscape():
@pytest.mark.core
def testCoreToken_HeaderFormat(mockGUI):
def testFmtToken_HeaderFormat(mockGUI):
"""Test the tokenization of header formats in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -422,7 +428,7 @@ def testCoreToken_HeaderFormat(mockGUI):
@pytest.mark.core
def testCoreToken_HeaderStyle(mockGUI):
def testFmtToken_HeaderStyle(mockGUI):
"""Test the styling of headers in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -436,11 +442,11 @@ def testCoreToken_HeaderStyle(mockGUI):
# No Styles
# =========
tokens.setTitleStyle(False, False)
tokens.setPartitionStyle(False, False)
tokens.setChapterStyle(False, False)
tokens.setSceneStyle(False, False)
assert tokens._titleStyle == Tokenizer.A_NONE
assert tokens._partStyle == Tokenizer.A_NONE
assert tokens._chapterStyle == Tokenizer.A_NONE
assert tokens._sceneStyle == Tokenizer.A_NONE
@@ -485,11 +491,11 @@ def testCoreToken_HeaderStyle(mockGUI):
# Center Headers
# ==============
tokens.setTitleStyle(True, False)
tokens.setPartitionStyle(True, False)
tokens.setChapterStyle(True, False)
tokens.setSceneStyle(True, False)
assert tokens._titleStyle == Tokenizer.A_CENTRE
assert tokens._partStyle == Tokenizer.A_CENTRE
assert tokens._chapterStyle == Tokenizer.A_CENTRE
assert tokens._sceneStyle == Tokenizer.A_CENTRE
@@ -534,11 +540,11 @@ def testCoreToken_HeaderStyle(mockGUI):
# Page Break Headers
# ==================
tokens.setTitleStyle(False, True)
tokens.setPartitionStyle(False, True)
tokens.setChapterStyle(False, True)
tokens.setSceneStyle(False, True)
assert tokens._titleStyle == Tokenizer.A_PBB
assert tokens._partStyle == Tokenizer.A_PBB
assert tokens._chapterStyle == Tokenizer.A_PBB
assert tokens._sceneStyle == Tokenizer.A_PBB
@@ -583,11 +589,11 @@ def testCoreToken_HeaderStyle(mockGUI):
# Page Break and Centre Headers
# =============================
tokens.setTitleStyle(True, True)
tokens.setPartitionStyle(True, True)
tokens.setChapterStyle(True, True)
tokens.setSceneStyle(True, True)
assert tokens._titleStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._partStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._chapterStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._sceneStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
@@ -634,11 +640,11 @@ def testCoreToken_HeaderStyle(mockGUI):
tokens._isNovel = True
# Title Styles
tokens.setTitleStyle(True, True)
tokens.setPartitionStyle(True, True)
tokens.setChapterStyle(False, False)
tokens.setSceneStyle(False, False)
assert tokens._titleStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._partStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._chapterStyle == Tokenizer.A_NONE
assert tokens._sceneStyle == Tokenizer.A_NONE
@@ -650,11 +656,11 @@ def testCoreToken_HeaderStyle(mockGUI):
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# Chapter Styles
tokens.setTitleStyle(False, False)
tokens.setPartitionStyle(False, False)
tokens.setChapterStyle(True, True)
tokens.setSceneStyle(False, False)
assert tokens._titleStyle == Tokenizer.A_NONE
assert tokens._partStyle == Tokenizer.A_NONE
assert tokens._chapterStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._sceneStyle == Tokenizer.A_NONE
@@ -666,11 +672,11 @@ def testCoreToken_HeaderStyle(mockGUI):
assert processStyle("##! Prologue\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
# Scene Styles
tokens.setTitleStyle(False, False)
tokens.setPartitionStyle(False, False)
tokens.setChapterStyle(False, False)
tokens.setSceneStyle(True, True)
assert tokens._titleStyle == Tokenizer.A_NONE
assert tokens._partStyle == Tokenizer.A_NONE
assert tokens._chapterStyle == Tokenizer.A_NONE
assert tokens._sceneStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
@@ -683,7 +689,7 @@ def testCoreToken_HeaderStyle(mockGUI):
@pytest.mark.core
def testCoreToken_MetaFormat(mockGUI):
def testFmtToken_MetaFormat(mockGUI):
"""Test the tokenization of meta formats in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -771,7 +777,7 @@ def testCoreToken_MetaFormat(mockGUI):
@pytest.mark.core
def testCoreToken_MarginFormat(mockGUI):
def testFmtToken_MarginFormat(mockGUI):
"""Test the tokenization of margin formats in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -814,7 +820,7 @@ def testCoreToken_MarginFormat(mockGUI):
@pytest.mark.core
def testCoreToken_ExtractFormats(mockGUI):
def testFmtToken_ExtractFormats(mockGUI):
"""Test the extraction of formats in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -921,7 +927,7 @@ def testCoreToken_ExtractFormats(mockGUI):
@pytest.mark.core
def testCoreToken_Paragraphs(mockGUI):
def testFmtToken_Paragraphs(mockGUI):
"""Test the splitting of paragraphs."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -994,7 +1000,7 @@ def testCoreToken_Paragraphs(mockGUI):
@pytest.mark.core
def testCoreToken_TextFormat(mockGUI):
def testFmtToken_TextFormat(mockGUI):
"""Test the tokenization of text formats in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -1085,7 +1091,7 @@ def testCoreToken_TextFormat(mockGUI):
@pytest.mark.core
def testCoreToken_Dialogue(mockGUI):
def testFmtToken_Dialogue(mockGUI):
"""Test the tokenization of dialogue in the Tokenizer class."""
CONFIG.fmtDQuoteOpen = "\u201c"
CONFIG.fmtDQuoteClose = "\u201d"
@@ -1182,7 +1188,7 @@ def testCoreToken_Dialogue(mockGUI):
@pytest.mark.core
def testCoreToken_SpecialFormat(mockGUI):
def testFmtToken_SpecialFormat(mockGUI):
"""Test the tokenization of special formats in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -1344,7 +1350,7 @@ def testCoreToken_SpecialFormat(mockGUI):
@pytest.mark.core
def testCoreToken_TextIndent(mockGUI):
def testFmtToken_TextIndent(mockGUI):
"""Test the handling of text indent in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
@@ -1427,7 +1433,7 @@ def testCoreToken_TextIndent(mockGUI):
@pytest.mark.core
def testCoreToken_ProcessHeaders(mockGUI):
def testFmtToken_ProcessHeaders(mockGUI):
"""Test the header and page parser of the Tokenizer class."""
project = NWProject()
project.data.setLanguage("en")
@@ -1441,7 +1447,7 @@ def testCoreToken_ProcessHeaders(mockGUI):
# H1: Title, First Page
assert tokens._isFirst is True
tokens._text = "# Part One\n"
tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
tokens.tokenizeText()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "T: Part One", [], Tokenizer.A_CENTRE),
@@ -1450,7 +1456,7 @@ def testCoreToken_ProcessHeaders(mockGUI):
# H1: Title, Not First Page
assert tokens._isFirst is False
tokens._text = "# Part One\n"
tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
tokens.tokenizeText()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "T: Part One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
@@ -1616,7 +1622,7 @@ def testCoreToken_ProcessHeaders(mockGUI):
@pytest.mark.core
def testCoreToken_BuildOutline(mockGUI, ipsumText):
def testFmtToken_BuildOutline(mockGUI, ipsumText):
"""Test stats counter of the Tokenizer class."""
project = NWProject()
project.data.setLanguage("en")
@@ -1680,7 +1686,7 @@ def testCoreToken_BuildOutline(mockGUI, ipsumText):
@pytest.mark.core
def testCoreToken_CountStats(mockGUI, ipsumText):
def testFmtToken_CountStats(mockGUI, ipsumText):
"""Test stats counter of the Tokenizer class."""
project = NWProject()
project.data.setLanguage("en")
@@ -1858,7 +1864,7 @@ def testCoreToken_CountStats(mockGUI, ipsumText):
)
tokens._counts = {}
tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
tokens.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}")
tokens.setSceneFormat("* * *", False)
tokens.setSynopsis(True)
@@ -1876,7 +1882,7 @@ def testCoreToken_CountStats(mockGUI, ipsumText):
@pytest.mark.core
def testCoreToken_SceneSeparators(mockGUI):
def testFmtToken_SceneSeparators(mockGUI):
"""Test the section and scene separators of the Tokenizer class."""
project = NWProject()
project.data.setLanguage("en")
@@ -1899,7 +1905,7 @@ def testCoreToken_SceneSeparators(mockGUI):
"###! Scene Four\n\n"
"Text\n\n"
)
md.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
md.setChapterFormat(f"C: {nwHeadFmt.TITLE}")
md.setSectionFormat("", True)
@@ -1953,7 +1959,7 @@ def testCoreToken_SceneSeparators(mockGUI):
"###! Scene Four\n\n"
"Text\n\n"
)
md.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
md.setChapterFormat(f"C: {nwHeadFmt.TITLE}")
md.setSectionFormat("", True)
@@ -2027,7 +2033,7 @@ def testCoreToken_SceneSeparators(mockGUI):
@pytest.mark.core
def testCoreToken_HeaderVisibility(mockGUI):
def testFmtToken_HeaderVisibility(mockGUI):
"""Test the heading visibility settings of the Tokenizer class."""
project = NWProject()
project.data.setLanguage("en")
@@ -2058,7 +2064,7 @@ def testCoreToken_HeaderVisibility(mockGUI):
md._isNovel = True
# Show All
md.setTitleFormat(nwHeadFmt.TITLE, False)
md.setPartitionFormat(nwHeadFmt.TITLE, False)
md.setChapterFormat(nwHeadFmt.TITLE, False)
md.setUnNumberedFormat(nwHeadFmt.TITLE, False)
md.setSceneFormat(nwHeadFmt.TITLE, False)
@@ -2086,7 +2092,7 @@ def testCoreToken_HeaderVisibility(mockGUI):
)
# Hide All
md.setTitleFormat(nwHeadFmt.TITLE, True)
md.setPartitionFormat(nwHeadFmt.TITLE, True)
md.setChapterFormat(nwHeadFmt.TITLE, True)
md.setUnNumberedFormat(nwHeadFmt.TITLE, True)
md.setSceneFormat(nwHeadFmt.TITLE, True)
@@ -2110,7 +2116,7 @@ def testCoreToken_HeaderVisibility(mockGUI):
md._isNovel = False
# Hide All
md.setTitleFormat(nwHeadFmt.TITLE, True)
md.setPartitionFormat(nwHeadFmt.TITLE, True)
md.setChapterFormat(nwHeadFmt.TITLE, True)
md.setUnNumberedFormat(nwHeadFmt.TITLE, True)
md.setSceneFormat(nwHeadFmt.TITLE, True)
@@ -2139,7 +2145,7 @@ def testCoreToken_HeaderVisibility(mockGUI):
@pytest.mark.core
def testCoreToken_CounterHandling(mockGUI):
def testFmtToken_CounterHandling(mockGUI):
"""Test the heading counter of the Tokenizer class."""
project = NWProject()
project.data.setLanguage("en")
@@ -2179,7 +2185,7 @@ def testCoreToken_CounterHandling(mockGUI):
"###! Scene Four\n\n"
"Text\n\n"
)
md.setTitleFormat(f"T: {nwHeadFmt.TITLE}")
md.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
md.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}")
md.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}")
md.setSceneFormat(
@@ -2224,7 +2230,7 @@ def testCoreToken_CounterHandling(mockGUI):
@pytest.mark.core
def testCoreToken_HeadingFormatter(fncPath, mockGUI, mockRnd):
def testFmtToken_HeadingFormatter(fncPath, mockGUI, mockRnd):
"""Check the HeadingFormatter class."""
project = NWProject()
project.setProjectLang("en_GB")
@@ -23,11 +23,11 @@ from __future__ import annotations
import pytest
from novelwriter.core.project import NWProject
from novelwriter.core.tomarkdown import ToMarkdown
from novelwriter.formats.tomarkdown import ToMarkdown
@pytest.mark.core
def testCoreToMarkdown_ConvertHeaders(mockGUI):
def testFmtToMarkdown_ConvertHeaders(mockGUI):
"""Test header formats in the ToMarkdown class."""
project = NWProject()
toMD = ToMarkdown(project)
@@ -73,7 +73,7 @@ def testCoreToMarkdown_ConvertHeaders(mockGUI):
@pytest.mark.core
def testCoreToMarkdown_ConvertParagraphs(mockGUI):
def testFmtToMarkdown_ConvertParagraphs(mockGUI):
"""Test paragraph formats in the ToMarkdown class."""
project = NWProject()
toMD = ToMarkdown(project)
@@ -209,7 +209,7 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI):
@pytest.mark.core
def testCoreToMarkdown_ConvertDirect(mockGUI):
def testFmtToMarkdown_ConvertDirect(mockGUI):
"""Test the converter directly using the ToMarkdown class."""
project = NWProject()
toMD = ToMarkdown(project)
@@ -246,7 +246,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
@pytest.mark.core
def testCoreToMarkdown_Save(mockGUI, fncPath):
def testFmtToMarkdown_Save(mockGUI, fncPath):
"""Test the save method of the ToMarkdown class."""
project = NWProject()
toMD = ToMarkdown(project)
@@ -293,12 +293,12 @@ def testCoreToMarkdown_Save(mockGUI, fncPath):
# ==========
saveFile = fncPath / "outFile.md"
toMD.saveMarkdown(saveFile)
toMD.saveDocument(saveFile)
assert saveFile.read_text(encoding="utf-8") == "".join(resText)
@pytest.mark.core
def testCoreToMarkdown_Format(mockGUI):
def testFmtToMarkdown_Format(mockGUI):
"""Test all the formatters for the ToMarkdown class."""
project = NWProject()
toMD = ToMarkdown(project)
@@ -30,7 +30,7 @@ import pytest
from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.core.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XMLParagraph, _mkTag
from novelwriter.formats.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XMLParagraph, _mkTag
from tests.tools import ODT_IGNORE, cmpFiles
@@ -54,7 +54,7 @@ def xmlToText(xElem):
@pytest.mark.core
def testCoreToOdt_Init(mockGUI):
def testFmtToOdt_Init(mockGUI):
"""Test initialisation of the ODT document."""
project = NWProject()
@@ -106,7 +106,7 @@ def testCoreToOdt_Init(mockGUI):
@pytest.mark.core
def testCoreToOdt_TextFormatting(mockGUI):
def testFmtToOdt_TextFormatting(mockGUI):
"""Test formatting of paragraphs."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
@@ -235,7 +235,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
@pytest.mark.core
def testCoreToOdt_DialogueFormatting(mockGUI):
def testFmtToOdt_DialogueFormatting(mockGUI):
"""Test formatting of dialogue."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
@@ -271,7 +271,7 @@ def testCoreToOdt_DialogueFormatting(mockGUI):
@pytest.mark.core
def testCoreToOdt_ConvertHeaders(mockGUI):
def testFmtToOdt_ConvertHeaders(mockGUI):
"""Test the converter of the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
@@ -358,7 +358,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
@pytest.mark.core
def testCoreToOdt_ConvertParagraphs(mockGUI):
def testFmtToOdt_ConvertParagraphs(mockGUI):
"""Test the converter of the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
@@ -706,7 +706,7 @@ def testCoreToOdt_ConvertParagraphs(mockGUI):
@pytest.mark.core
def testCoreToOdt_ConvertDirect(mockGUI):
def testFmtToOdt_ConvertDirect(mockGUI):
"""Test the converter directly using the ToOdt class to reach some
otherwise hard to reach conditions.
"""
@@ -757,7 +757,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
@pytest.mark.core
def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
def testFmtToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
"""Test the document save functions."""
project = NWProject()
project.data.setAuthor("Jane Smith")
@@ -772,8 +772,6 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
assert odt._dLanguage == ""
odt.setLanguage("nb_NO")
assert odt._dLanguage == "nb"
odt.setColourHeaders(True)
assert odt._colourHead is True
odt.setHeaderFormat(nwHeadFmt.ODT_AUTO, 1)
assert odt._headerFormat == nwHeadFmt.ODT_AUTO
odt.setFirstLineIndent(True, 1.4, False)
@@ -804,7 +802,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
testFile = tstPaths.outDir / "coreToOdt_SaveFlat_document.fodt"
compFile = tstPaths.refDir / "coreToOdt_SaveFlat_document.fodt"
odt.saveFlatXML(flatFile)
odt.saveDocument(flatFile)
assert flatFile.exists()
copyfile(flatFile, testFile)
@@ -812,7 +810,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
@pytest.mark.core
def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
def testFmtToOdt_SaveFull(mockGUI, fncPath, tstPaths):
"""Test the document save functions."""
project = NWProject()
project.data.setAuthor("Jane Smith")
@@ -839,7 +837,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
fullFile = fncPath / "document.odt"
odt.saveOpenDocText(fullFile)
odt.saveDocument(fullFile)
assert fullFile.exists()
assert zipfile.is_zipfile(fullFile)
@@ -890,7 +888,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
@pytest.mark.core
def testCoreToOdt_SpecialFormats(mockGUI):
def testFmtToOdt_SpecialFormats(mockGUI):
"""Test the special formatters for the ToOdt class."""
project = NWProject()
odt = ToOdt(project, isFlat=True)
@@ -921,7 +919,7 @@ def testCoreToOdt_SpecialFormats(mockGUI):
@pytest.mark.core
def testCoreToOdt_ODTParagraphStyle():
def testFmtToOdt_ODTParagraphStyle():
"""Test the ODTParagraphStyle class."""
parStyle = ODTParagraphStyle("test")
@@ -1158,7 +1156,7 @@ def testCoreToOdt_ODTParagraphStyle():
@pytest.mark.core
def testCoreToOdt_ODTTextStyle():
def testFmtToOdt_ODTTextStyle():
"""Test the ODTTextStyle class."""
txtStyle = ODTTextStyle("test")
@@ -1308,7 +1306,7 @@ def testCoreToOdt_ODTTextStyle():
@pytest.mark.core
def testCoreToOdt_XMLParagraph():
def testFmtToOdt_XMLParagraph():
"""Test XML encoding of paragraph."""
# Stage 1 : Text
# ==============
@@ -1497,7 +1495,7 @@ def testCoreToOdt_XMLParagraph():
@pytest.mark.core
def testCoreToOdt_MkTag():
def testFmtToOdt_MkTag():
"""Test the tag maker function."""
assert _mkTag("office", "text") == "{urn:oasis:names:tc:opendocument:xmlns:office:1.0}text"
assert _mkTag("style", "text") == "{urn:oasis:names:tc:opendocument:xmlns:style:1.0}text"
@@ -27,7 +27,7 @@ from PyQt5.QtGui import QColor, QTextBlock, QTextCharFormat, QTextCursor
from novelwriter import CONFIG
from novelwriter.constants import nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.core.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
QtPageBreakAfter, QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
@@ -56,11 +56,12 @@ def charFmtInBlock(block: QTextBlock, pos: int) -> QTextCharFormat:
@pytest.mark.core
def testCoreToQTextDocument_ConvertHeaders(mockGUI):
def testFmtToQTextDocument_ConvertHeaders(mockGUI):
"""Test header formats in the ToQTextDocument class."""
project = NWProject()
qdoc = ToQTextDocument(project)
qdoc.initDocument(CONFIG.textFont, THEME)
qdoc.saveDocument("") # Doesn't do anything for this format
qdoc._isNovel = True
qdoc._isFirst = True
@@ -132,7 +133,7 @@ def testCoreToQTextDocument_ConvertHeaders(mockGUI):
@pytest.mark.core
def testCoreToQTextDocument_SeparatorSkip(mockGUI):
def testFmtToQTextDocument_SeparatorSkip(mockGUI):
"""Test separator and skip in the ToQTextDocument class."""
project = NWProject()
qdoc = ToQTextDocument(project)
@@ -198,7 +199,7 @@ def testCoreToQTextDocument_SeparatorSkip(mockGUI):
@pytest.mark.core
def testCoreToQTextDocument_NovelMeta(mockGUI):
def testFmtToQTextDocument_NovelMeta(mockGUI):
"""Test novel meta formats in the ToQTextDocument class."""
project = NWProject()
qdoc = ToQTextDocument(project)
@@ -275,7 +276,7 @@ def testCoreToQTextDocument_NovelMeta(mockGUI):
@pytest.mark.core
def testCoreToQTextDocument_NoteMeta(mockGUI):
def testFmtToQTextDocument_NoteMeta(mockGUI):
"""Test note meta formats in the ToQTextDocument class."""
project = NWProject()
qdoc = ToQTextDocument(project)
@@ -332,7 +333,7 @@ def testCoreToQTextDocument_NoteMeta(mockGUI):
@pytest.mark.core
def testCoreToQTextDocument_TextBlockFormats(mockGUI):
def testFmtToQTextDocument_TextBlockFormats(mockGUI):
"""Test text block formats in the ToQTextDocument class."""
project = NWProject()
qdoc = ToQTextDocument(project)
@@ -441,7 +442,7 @@ def testCoreToQTextDocument_TextBlockFormats(mockGUI):
@pytest.mark.core
def testCoreToQTextDocument_TextCharFormats(mockGUI):
def testFmtToQTextDocument_TextCharFormats(mockGUI):
"""Test text char formats in the ToQTextDocument class."""
CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO
CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO
@@ -576,7 +577,7 @@ def testCoreToQTextDocument_TextCharFormats(mockGUI):
@pytest.mark.core
def testCoreToQTextDocument_Footnotes(mockGUI):
def testFmtToQTextDocument_Footnotes(mockGUI):
"""Test footnotes in the ToQTextDocument class."""
project = NWProject()
qdoc = ToQTextDocument(project)
+1 -1
View File
@@ -27,8 +27,8 @@ from PyQt5.QtGui import QMouseEvent, QTextCursor
from PyQt5.QtWidgets import QAction, QApplication, QMenu
from novelwriter import CONFIG, SHARED
from novelwriter.core.toqdoc import ToQTextDocument
from novelwriter.enum import nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.gui.docviewer import GuiDocViewer
from novelwriter.types import QtModNone, QtMouseLeft
+22 -14
View File
@@ -311,14 +311,14 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
"""Test the Headings Tab of the GuiBuildSettings dialog."""
build = BuildSettings()
ttTitle = f"Title: {nwHeadFmt.TITLE}"
ttTitle = f"Part: {nwHeadFmt.TITLE}"
chTitle = f"Chapter: {nwHeadFmt.TITLE}"
unTitle = f"Interlude: {nwHeadFmt.TITLE}"
scTitle = f"Scene: {nwHeadFmt.TITLE}"
shTitle = f"Hard Scene: {nwHeadFmt.TITLE}"
sxTitle = f"Section: {nwHeadFmt.TITLE}"
build.setValue("headings.fmtTitle", ttTitle)
build.setValue("headings.fmtPart", ttTitle)
build.setValue("headings.fmtChapter", chTitle)
build.setValue("headings.fmtUnnumbered", unTitle)
build.setValue("headings.fmtScene", scTitle)
@@ -337,7 +337,7 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
assert bSettings.toolStack.currentWidget() is headTab
# Check initial values
assert headTab.fmtTitle.text() == ttTitle
assert headTab.fmtPart.text() == ttTitle
assert headTab.fmtChapter.text() == chTitle
assert headTab.fmtUnnumbered.text() == unTitle
assert headTab.fmtScene.text() == scTitle
@@ -356,7 +356,7 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
assert headTab.editTextBox.isEnabled() is False
# Title
headTab.btnTitle.click()
headTab.btnPart.click()
assert headTab._editing == headTab.EDIT_TITLE
assert headTab.editTextBox.isEnabled() is True
assert headTab.editTextBox.toPlainText() == ttTitle
@@ -434,10 +434,10 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
)
# Set all to plain title
headTab.btnTitle.click()
headTab.btnPart.click()
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
headTab.btnApply.click()
assert build.getStr("headings.fmtTitle") == nwHeadFmt.TITLE
assert build.getStr("headings.fmtPart") == nwHeadFmt.TITLE
headTab.btnChapter.click()
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
@@ -707,9 +707,11 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI):
"""Test the format-specific settings."""
build = BuildSettings()
build.setValue("odt.addColours", False)
build.setValue("odt.pageHeader", nwHeadFmt.ODT_AUTO)
build.setValue("odt.pageCountOffset", 0)
build.setValue("odt.colorHeadings", True)
build.setValue("odt.scaleHeadings", True)
build.setValue("odt.boldHeadings", True)
build.setValue("html.addStyles", False)
build.setValue("html.preserveTabs", False)
@@ -724,28 +726,34 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI):
assert bSettings.toolStack.currentWidget() is fmtTab
# Check initial values
assert fmtTab.odtAddColours.isChecked() is False
assert fmtTab.odtPageHeader.text() == nwHeadFmt.ODT_AUTO
assert fmtTab.odtPageCountOffset.value() == 0
assert fmtTab.colorHeadings.isChecked() is True
assert fmtTab.scaleHeadings.isChecked() is True
assert fmtTab.boldHeadings.isChecked() is True
assert fmtTab.htmlAddStyles.isChecked() is False
assert fmtTab.htmlPreserveTabs.isChecked() is False
# Toggle all
fmtTab.odtAddColours.setChecked(True)
fmtTab.htmlAddStyles.setChecked(True)
fmtTab.htmlPreserveTabs.setChecked(True)
# Change Values
fmtTab.odtPageCountOffset.setValue(1)
fmtTab.odtPageHeader.setText("Stuff")
# Toggle all
fmtTab.colorHeadings.setChecked(False)
fmtTab.scaleHeadings.setChecked(False)
fmtTab.boldHeadings.setChecked(False)
fmtTab.htmlAddStyles.setChecked(True)
fmtTab.htmlPreserveTabs.setChecked(True)
# Save values
fmtTab.saveContent()
assert build.getBool("odt.addColours") is True
assert build.getStr("odt.pageHeader") == "Stuff"
assert build.getInt("odt.pageCountOffset") == 1
assert build.getBool("odt.colorHeadings") is False
assert build.getBool("odt.scaleHeadings") is False
assert build.getBool("odt.boldHeadings") is False
assert build.getBool("html.addStyles") is True
assert build.getBool("html.preserveTabs") is True