Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend heading styling options #2045

Merged
merged 13 commits into from
Oct 15, 2024
47 changes: 25 additions & 22 deletions novelwriter/core/buildsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,30 @@

# 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),
Expand All @@ -77,7 +78,7 @@
"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),
Expand All @@ -94,9 +95,11 @@
"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),
}
Expand All @@ -108,12 +111,16 @@
"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"),
Expand Down Expand Up @@ -147,12 +154,14 @@
"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"),
}
Expand Down Expand Up @@ -346,18 +355,12 @@ def setAllowRoot(self, tHandle: str, state: bool) -> None:
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
Expand Down
50 changes: 25 additions & 25 deletions novelwriter/core/docbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
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__)

Expand Down Expand Up @@ -178,10 +178,7 @@ def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int,
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)
Expand Down Expand Up @@ -213,10 +210,7 @@ def iterBuildHTML(self, path: Path | None, asJson: bool = False) -> Iterable[tup

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)
Expand Down Expand Up @@ -246,7 +240,7 @@ def iterBuildMarkdown(self, path: Path, extendedMd: bool) -> Iterable[tuple[int,
self._cache = makeObj

try:
makeObj.saveMarkdown(path)
makeObj.saveDocument(path)
except Exception as exc:
logException()
self._error = formatException(exc)
Expand Down Expand Up @@ -276,10 +270,7 @@ def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tupl

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)
Expand All @@ -297,9 +288,9 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
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"),
Expand All @@ -322,8 +313,12 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
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"),
Expand All @@ -343,6 +338,11 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
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"))
Expand All @@ -355,10 +355,10 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
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)
Expand Down
86 changes: 43 additions & 43 deletions novelwriter/core/tohtml.py → novelwriter/formats/tohtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
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__)
Expand Down Expand Up @@ -290,51 +290,51 @@ def appendFootnotes(self) -> None:

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:
Expand Down
Loading