diff --git a/nw/constants/__init__.py b/nw/constants/__init__.py
index b58b56c3..1a6748a1 100644
--- a/nw/constants/__init__.py
+++ b/nw/constants/__init__.py
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from nw.constants.iso import isoLanguage, isoCountry
from nw.constants.constants import (
- nwConst, nwLists, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode
+ nwConst, nwLists, nwRegEx, nwFiles, nwKeyWords, nwLabels, nwQuotes,
+ nwUnicode, nwHtmlUnicode
)
from nw.constants.enum import (
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline,
@@ -19,6 +20,7 @@ __all__ = [
"nwLabels",
"nwQuotes",
"nwUnicode",
+ "nwHtmlUnicode",
"nwAlert",
"nwDocAction",
"nwItemClass",
diff --git a/nw/constants/constants.py b/nw/constants/constants.py
index 5e4581b9..0909b1f2 100644
--- a/nw/constants/constants.py
+++ b/nw/constants/constants.py
@@ -265,7 +265,7 @@ class nwUnicode:
U_LCQUO = "\u300c" # Left corner bracket
U_RCQUO = "\u300d" # Right corner bracket
U_LWCQUO = "\u300e" # Left white corner bracket
- U_RECQUO = "\u300f" # Right white corner bracket
+ U_RWCQUO = "\u300f" # Right white corner bracket
## Punctuation
U_FGDASH = "\u2012" # Figure dash
@@ -331,7 +331,7 @@ class nwUnicode:
H_LCQUO = "「"
H_RCQUO = "」"
H_LWCQUO = "『"
- H_LWCQUO = "『"
+ H_RWCQUO = "』"
## Punctuation
H_FGDASH = "‒"
@@ -374,3 +374,60 @@ class nwUnicode:
H_LTRIS = "◂"
# END Class nwUnicode
+
+class nwHtmlUnicode():
+
+ U_TO_H = {
+ ## Quotes
+ nwUnicode.U_QUOT : nwUnicode.H_QUOT,
+ nwUnicode.U_APOS : nwUnicode.H_APOS,
+ nwUnicode.U_LAQUO : nwUnicode.H_LAQUO,
+ nwUnicode.U_RAQUO : nwUnicode.H_RAQUO,
+ nwUnicode.U_LSQUO : nwUnicode.H_LSQUO,
+ nwUnicode.U_RSQUO : nwUnicode.H_RSQUO,
+ nwUnicode.U_SBQUO : nwUnicode.H_SBQUO,
+ nwUnicode.U_SUQUO : nwUnicode.H_SUQUO,
+ nwUnicode.U_LDQUO : nwUnicode.H_LDQUO,
+ nwUnicode.U_RDQUO : nwUnicode.H_RDQUO,
+ nwUnicode.U_BDQUO : nwUnicode.H_BDQUO,
+ nwUnicode.U_UDQUO : nwUnicode.H_UDQUO,
+ nwUnicode.U_LSAQUO : nwUnicode.H_LSAQUO,
+ nwUnicode.U_RSAQUO : nwUnicode.H_RSAQUO,
+ nwUnicode.U_BDRQUO : nwUnicode.H_BDRQUO,
+ nwUnicode.U_LCQUO : nwUnicode.H_LCQUO,
+ nwUnicode.U_RCQUO : nwUnicode.H_RCQUO,
+ nwUnicode.U_LWCQUO : nwUnicode.H_LWCQUO,
+ nwUnicode.U_RWCQUO : nwUnicode.H_RWCQUO,
+
+ ## Punctuation
+ nwUnicode.U_FGDASH : nwUnicode.H_FGDASH,
+ nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
+ nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
+ nwUnicode.U_HBAR : nwUnicode.H_HBAR,
+ nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
+ nwUnicode.U_MAPOSS : nwUnicode.H_MAPOSS,
+ nwUnicode.U_PRIME : nwUnicode.H_PRIME,
+ nwUnicode.U_DPRIME : nwUnicode.H_DPRIME,
+
+ ## Spaces
+ nwUnicode.U_NBSP : nwUnicode.H_NBSP,
+ nwUnicode.U_THSP : nwUnicode.H_THSP,
+ nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
+ nwUnicode.U_ENSP : nwUnicode.H_ENSP,
+ nwUnicode.U_EMSP : nwUnicode.H_EMSP,
+
+ ## Symbols
+ nwUnicode.U_CHECK : nwUnicode.H_CHECK,
+ nwUnicode.U_CROSS : nwUnicode.H_CROSS,
+ nwUnicode.U_BULL : nwUnicode.H_BULL,
+ nwUnicode.U_TRBULL : nwUnicode.H_TRBULL,
+ nwUnicode.U_HYBULL : nwUnicode.H_HYBULL,
+ nwUnicode.U_FLOWER : nwUnicode.H_FLOWER,
+ nwUnicode.U_PERMIL : nwUnicode.H_PERMIL,
+ nwUnicode.U_DEGREE : nwUnicode.H_DEGREE,
+ nwUnicode.U_MINUS : nwUnicode.H_MINUS,
+ nwUnicode.U_TIMES : nwUnicode.H_TIMES,
+ nwUnicode.U_DIVIDE : nwUnicode.H_DIVIDE,
+ }
+
+# END Class nwHtmlUnicode
diff --git a/nw/core/options.py b/nw/core/options.py
index c7849bb0..52a30af5 100644
--- a/nw/core/options.py
+++ b/nw/core/options.py
@@ -73,12 +73,14 @@ class OptionState():
"excludeBody",
"textFont",
"textSize",
+ "lineHeight",
"noStyling",
"incSynopsis",
"incComments",
"incKeywords",
"incBodyText",
"replaceTabs",
+ "replaceUCode",
},
"GuiOutline": {
"headerOrder",
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index 3aa6f5fc..67ebcc0f 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -25,10 +25,9 @@ along with this program. If not, see .
"""
import logging
-import re
from nw.core.tokenizer import Tokenizer
-from nw.constants import nwUnicode, nwLabels, nwKeyWords
+from nw.constants import nwLabels, nwKeyWords, nwHtmlUnicode
logger = logging.getLogger(__name__)
@@ -41,27 +40,13 @@ class ToHtml(Tokenizer):
def __init__(self, theProject, theParent):
Tokenizer.__init__(self, theProject, theParent)
- self.genMode = self.M_EXPORT
+ self.genMode = self.M_EXPORT
self.cssStyles = True
+ self.fullHTML = []
- self.repDict = {
- "<" : "<",
- ">" : ">",
- "&" : "&",
- nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
- nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
- nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
- nwUnicode.U_NBSP : nwUnicode.H_NBSP,
- nwUnicode.U_THSP : nwUnicode.H_THSP,
- nwUnicode.U_THNBSP : nwUnicode.H_THNBSP,
- nwUnicode.U_MAPOSS : nwUnicode.H_RSQUO,
- }
- self.revDict = {}
- self.reReplace = []
- self.reReverse = []
- self._buildRegEx()
-
- self.fullHTML = []
+ # Internals
+ self._trMap = {}
+ self.setReplaceUnicode(False)
return
@@ -88,6 +73,23 @@ class ToHtml(Tokenizer):
self.cssStyles = cssStyles
return
+ def setReplaceUnicode(self, doReplace):
+ """Set the translation map to either minimal or full unicode to
+ html entities replacement.
+ """
+ # Control characters must always be replaced
+ self._trMap = str.maketrans({
+ "<" : "<",
+ ">" : ">",
+ "&" : "&",
+ })
+
+ if doReplace:
+ # Extend to all relevant Unicode characters
+ self._trMap.update(str.maketrans(nwHtmlUnicode.U_TO_H))
+
+ return
+
##
# Class Methods
##
@@ -97,30 +99,12 @@ class ToHtml(Tokenizer):
"""
return sum([len(x) for x in self.fullHTML])
- def doAutoReplace(self):
+ def doPreProcessing(self):
"""Extend the auto-replace to also properly encode some unicode
characters into their respective HTML entities.
"""
- Tokenizer.doAutoReplace(self)
- self.theText = self.reReplace.sub(
- lambda x: self.repDict[x.group(0)], self.theText
- )
- return
-
- def doPostProcessing(self):
- """Reverse the html entities replacement on the markdown text.
- Otherwise, all the &something; bits will also be in there.
- """
- Tokenizer.doPostProcessing(self)
- if self.genMode == self.M_PREVIEW:
- # Doesn't matter for preview as we don't use the markdown
- return
-
- if self.keepMarkdown:
- self.theMarkdown[-1] = self.reReverse.sub(
- lambda x: self.revDict[x.group(0)], self.theMarkdown[-1]
- )
-
+ Tokenizer.doPreProcessing(self)
+ self.theText = self.theText.translate(self._trMap)
return
def doConvert(self):
@@ -331,21 +315,84 @@ class ToHtml(Tokenizer):
if not self.cssStyles:
return theStyles
+ mScale = self.lineHeight/1.15
textAlign = "justify" if self.doJustify else "left"
- theStyles.append("body {font-family: '%s'; font-size: %dpt}" % (
- self.textFont, self.textSize)
- )
- theStyles.append("p {text-align: %s;}" % textAlign)
- theStyles.append("h1, h2 {color: rgb(66, 113, 174);}")
- theStyles.append("h3, h4 {color: rgb(50, 50, 50);}")
- theStyles.append("h1, h2, h3, h4 {page-break-after: avoid;}")
+ theStyles.append("body {font-family: '%s'; font-size: %dpt;}" % (
+ self.textFont, self.textSize
+ ))
+ theStyles.append((
+ "p {"
+ "text-align: %s; line-height: %d%%; "
+ "margin-top: %.2fem; margin-bottom: %.2fem;"
+ "}"
+ ) % (
+ textAlign,
+ round(100 * self.lineHeight),
+ mScale * self.marginText[0],
+ mScale * self.marginText[1],
+ ))
+ theStyles.append((
+ "h1 {"
+ "color: rgb(66, 113, 174); "
+ "page-break-after: avoid; "
+ "margin-top: %.2fem; "
+ "margin-bottom: %.2fem;"
+ "}"
+ ) % (
+ mScale * self.marginHead1[0], mScale * self.marginHead1[1]
+ ))
+ theStyles.append((
+ "h2 {"
+ "color: rgb(66, 113, 174); "
+ "page-break-after: avoid; "
+ "margin-top: %.2fem; "
+ "margin-bottom: %.2fem;"
+ "}"
+ ) % (
+ mScale * self.marginHead2[0], mScale * self.marginHead2[1]
+ ))
+ theStyles.append((
+ "h3 {"
+ "color: rgb(50, 50, 50); "
+ "page-break-after: avoid; "
+ "margin-top: %.2fem; "
+ "margin-bottom: %.2fem;"
+ "}"
+ ) % (
+ mScale * self.marginHead3[0], mScale * self.marginHead3[1]
+ ))
+ theStyles.append((
+ "h4 {"
+ "color: rgb(50, 50, 50); "
+ "page-break-after: avoid; "
+ "margin-top: %.2fem; "
+ "margin-bottom: %.2fem;"
+ "}"
+ ) % (
+ mScale * self.marginHead4[0], mScale * self.marginHead4[1]
+ ))
+ theStyles.append((
+ ".title {"
+ "font-size: 2.5em; "
+ "margin-top: %.2fem; "
+ "margin-bottom: %.2fem;"
+ "}"
+ ) % (
+ mScale * self.marginTitle[0], mScale * self.marginTitle[1]
+ ))
+ theStyles.append((
+ ".sep, .skip {"
+ "text-align: center; "
+ "margin-top: %.2fem; "
+ "margin-bottom: %.2fem;}"
+ ) % (
+ mScale, mScale
+ ))
+
theStyles.append("a {color: rgb(66, 113, 174);}")
- theStyles.append(".title {font-size: 2.5em;}")
theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
theStyles.append(".break {text-align: left;}")
- theStyles.append(".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}")
- theStyles.append(".skip {margin-top: 1em; margin-bottom: 1em;}")
theStyles.append(".synopsis {font-style: italic;}")
theStyles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
@@ -403,16 +450,4 @@ class ToHtml(Tokenizer):
return retText
- def _buildRegEx(self):
- """Build the regular expressions
- """
- self.revDict = dict(map(reversed, self.repDict.items()))
- self.reReplace = re.compile(
- "|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL
- )
- self.reReverse = re.compile(
- "|".join([re.escape(k) for k in self.revDict.keys()]), flags=re.DOTALL
- )
- return
-
# END Class ToHtml
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index 96b4be2d..3e0474ae 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -32,7 +32,7 @@ from PyQt5.QtCore import QRegularExpression
from nw.core.document import NWDoc
from nw.core.tools import numberToWord, numberToRoman
-from nw.constants import nwConst, nwItemLayout, nwItemType, nwRegEx
+from nw.constants import nwConst, nwUnicode, nwItemLayout, nwItemType, nwRegEx
logger = logging.getLogger(__name__)
@@ -86,7 +86,7 @@ class Tokenizer():
self.theResult = "" # The result of the last document
self.keepMarkdown = False # Whether to keep the markdown text
- self.theMarkdown = [] # The result novelWriter markdown of all documents
+ self.theMarkdown = [] # The result novelWriter markdown of all documents
# User Settings
self.textFont = "Serif" # Output text font
@@ -297,9 +297,10 @@ class Tokenizer():
return True
- def doAutoReplace(self):
- """Run through the user's auto-replace dictionary.
+ def doPreProcessing(self):
+ """Reun trough the various replace doctionaries.
"""
+ # Process the user's auto-replace dictionary
if len(self.theProject.autoReplace) > 0:
repDict = {}
for aKey, aVal in self.theProject.autoReplace.items():
@@ -307,6 +308,10 @@ class Tokenizer():
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
+ # Process the character translation map
+ trDict = {nwUnicode.U_MAPOSS: nwUnicode.U_RSQUO}
+ self.theText = self.theText.translate(str.maketrans(trDict))
+
return
def doPostProcessing(self):
diff --git a/nw/core/toodt.py b/nw/core/toodt.py
index ee83673c..b9cffc3c 100644
--- a/nw/core/toodt.py
+++ b/nw/core/toodt.py
@@ -194,23 +194,25 @@ class ToOdt(Tokenizer):
self._fSizeHead = f"{round(1.15 * self.textSize):d}pt"
self._fSizeText = f"{self.textSize:d}pt"
- self._mTopTitle = self._emToCm(self.marginTitle[0])
- self._mTopHead1 = self._emToCm(self.marginHead1[0])
- self._mTopHead2 = self._emToCm(self.marginHead2[0])
- self._mTopHead3 = self._emToCm(self.marginHead3[0])
- self._mTopHead4 = self._emToCm(self.marginHead4[0])
- self._mTopHead = self._emToCm(self.marginHead4[0])
- self._mTopText = self._emToCm(self.marginText[0])
- self._mTopMeta = self._emToCm(self.marginMeta[0])
+ mScale = self.lineHeight/1.15
- self._mBotTitle = self._emToCm(self.marginTitle[1])
- self._mBotHead1 = self._emToCm(self.marginHead1[1])
- self._mBotHead2 = self._emToCm(self.marginHead2[1])
- self._mBotHead3 = self._emToCm(self.marginHead3[1])
- self._mBotHead4 = self._emToCm(self.marginHead4[1])
- self._mBotHead = self._emToCm(self.marginHead4[1])
- self._mBotText = self._emToCm(self.marginText[1])
- self._mBotMeta = self._emToCm(self.marginMeta[1])
+ self._mTopTitle = self._emToCm(mScale * self.marginTitle[0])
+ self._mTopHead1 = self._emToCm(mScale * self.marginHead1[0])
+ self._mTopHead2 = self._emToCm(mScale * self.marginHead2[0])
+ self._mTopHead3 = self._emToCm(mScale * self.marginHead3[0])
+ self._mTopHead4 = self._emToCm(mScale * self.marginHead4[0])
+ self._mTopHead = self._emToCm(mScale * self.marginHead4[0])
+ self._mTopText = self._emToCm(mScale * self.marginText[0])
+ self._mTopMeta = self._emToCm(mScale * self.marginMeta[0])
+
+ self._mBotTitle = self._emToCm(mScale * self.marginTitle[1])
+ self._mBotHead1 = self._emToCm(mScale * self.marginHead1[1])
+ self._mBotHead2 = self._emToCm(mScale * self.marginHead2[1])
+ self._mBotHead3 = self._emToCm(mScale * self.marginHead3[1])
+ self._mBotHead4 = self._emToCm(mScale * self.marginHead4[1])
+ self._mBotHead = self._emToCm(mScale * self.marginHead4[1])
+ self._mBotText = self._emToCm(mScale * self.marginText[1])
+ self._mBotMeta = self._emToCm(mScale * self.marginMeta[1])
if self.colourHead:
self._colHead12 = "#2a6099"
diff --git a/nw/gui/build.py b/nw/gui/build.py
index a252fc9d..3b76248f 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -41,7 +41,7 @@ from PyQt5.QtWidgets import (
qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget,
- QSizePolicy
+ QSizePolicy, QDoubleSpinBox
)
from nw.common import fuzzyTime, makeFileNameSafe
@@ -55,15 +55,17 @@ logger = logging.getLogger(__name__)
class GuiBuildNovel(QDialog):
- FMT_ODT = 1
- FMT_FODT = 2
- FMT_PDF = 3
- FMT_HTM = 4
- FMT_MD = 5
- FMT_GH = 6
- FMT_NWD = 7
- FMT_JSON_H = 8
- FMT_JSON_M = 9
+ FMT_PDF = 1 # Print to PDF
+
+ FMT_ODT = 2 # Open Document file
+ FMT_FODT = 3 # Flat Open Document file
+ FMT_HTM = 4 # HTML5
+ FMT_NWD = 5 # nW Markdown
+ FMT_MD = 6 # Standard Markdown
+ FMT_GH = 7 # GitHub Markdown
+
+ FMT_JSON_H = 8 # HTML5 wrapped in JSON
+ FMT_JSON_M = 9 # nW Markdown wrapped in JSON
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
@@ -93,6 +95,9 @@ class GuiBuildNovel(QDialog):
self.docView = GuiBuildNovelDocView(self, self.theProject)
+ hS = self.theTheme.fontPixelSize
+ wS = 2*hS
+
# Title Formats
# =============
@@ -164,33 +169,39 @@ class GuiBuildNovel(QDialog):
self.boxTitle.addWidget(self.fmtTitle)
self.boxChapter = QHBoxLayout()
self.boxChapter.addWidget(self.fmtChapter)
- self.boxUnnumbered = QHBoxLayout()
- self.boxUnnumbered.addWidget(self.fmtUnnumbered)
+ self.boxUnnumb = QHBoxLayout()
+ self.boxUnnumb.addWidget(self.fmtUnnumbered)
self.boxScene = QHBoxLayout()
self.boxScene.addWidget(self.fmtScene)
self.boxSection = QHBoxLayout()
self.boxSection.addWidget(self.fmtSection)
- self.titleForm.addWidget(QLabel("Title"), 0, 0, 1, 1, Qt.AlignLeft)
- self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight)
- self.titleForm.addWidget(QLabel("Chapter"), 1, 0, 1, 1, Qt.AlignLeft)
- self.titleForm.addLayout(self.boxChapter, 1, 1, 1, 1, Qt.AlignRight)
- self.titleForm.addWidget(QLabel("Unnumbered"), 2, 0, 1, 1, Qt.AlignLeft)
- self.titleForm.addLayout(self.boxUnnumbered, 2, 1, 1, 1, Qt.AlignRight)
- self.titleForm.addWidget(QLabel("Scene"), 3, 0, 1, 1, Qt.AlignLeft)
- self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight)
- self.titleForm.addWidget(QLabel("Section"), 4, 0, 1, 1, Qt.AlignLeft)
- self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight)
+ titleLabel = QLabel("Title")
+ chapterLabel = QLabel("Chapter")
+ unnumbLabel = QLabel("Unnumbered")
+ sceneLabel = QLabel("Scene")
+ sectionLabel = QLabel("Section")
+
+ self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft)
+ self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight)
+ self.titleForm.addWidget(chapterLabel, 1, 0, 1, 1, Qt.AlignLeft)
+ self.titleForm.addLayout(self.boxChapter, 1, 1, 1, 1, Qt.AlignRight)
+ self.titleForm.addWidget(unnumbLabel, 2, 0, 1, 1, Qt.AlignLeft)
+ self.titleForm.addLayout(self.boxUnnumb, 2, 1, 1, 1, Qt.AlignRight)
+ self.titleForm.addWidget(sceneLabel, 3, 0, 1, 1, Qt.AlignLeft)
+ self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight)
+ self.titleForm.addWidget(sectionLabel, 4, 0, 1, 1, Qt.AlignLeft)
+ self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight)
self.titleForm.setColumnStretch(0, 0)
self.titleForm.setColumnStretch(1, 1)
- # Text Options
- # =============
+ # Font Options
+ # ============
- self.formatGroup = QGroupBox("Formatting Options", self)
- self.formatForm = QGridLayout(self)
- self.formatGroup.setLayout(self.formatForm)
+ self.fontGroup = QGroupBox("Font Options", self)
+ self.fontForm = QGridLayout(self)
+ self.fontGroup.setLayout(self.fontForm)
## Font Family
self.textFont = QLineEdit()
@@ -204,98 +215,111 @@ class GuiBuildNovel(QDialog):
self.fontButton.clicked.connect(self._selectFont)
self.textSize = QSpinBox(self)
- self.textSize.setFixedWidth(5*self.theTheme.textNWidth)
+ self.textSize.setFixedWidth(6*self.theTheme.textNWidth)
self.textSize.setMinimum(6)
self.textSize.setMaximum(72)
self.textSize.setSingleStep(1)
- self.textSize.setToolTip(
- "The size is used for PDF and printing. Other formats have no size set."
- )
self.textSize.setValue(
self.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
)
- self.justifyText = QSwitch()
- self.justifyText.setToolTip(
- "Applies to PDF, printing, HTML, and Open Document exports."
- )
- self.justifyText.setChecked(
- self.optState.getBool("GuiBuildNovel", "justifyText", False)
- )
-
- self.noStyling = QSwitch()
- self.noStyling.setToolTip(
- "Disable all styling of the text."
- )
- self.noStyling.setChecked(
- self.optState.getBool("GuiBuildNovel", "noStyling", False)
+ self.lineHeight = QDoubleSpinBox(self)
+ self.lineHeight.setFixedWidth(6*self.theTheme.textNWidth)
+ self.lineHeight.setMinimum(0.8)
+ self.lineHeight.setMaximum(3.0)
+ self.lineHeight.setSingleStep(0.05)
+ self.lineHeight.setDecimals(2)
+ self.lineHeight.setValue(
+ self.optState.getFloat("GuiBuildNovel", "lineHeight", 1.15)
)
# Dummy box due to QGridView and QLineEdit expand bug
self.boxFont = QHBoxLayout()
self.boxFont.addWidget(self.textFont)
- self.formatForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft)
- self.formatForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight)
- self.formatForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight)
- self.formatForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft)
- self.formatForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight)
- self.formatForm.addWidget(QLabel("Justify text"), 2, 0, 1, 1, Qt.AlignLeft)
- self.formatForm.addWidget(self.justifyText, 2, 1, 1, 2, Qt.AlignRight)
- self.formatForm.addWidget(QLabel("Disable styling"), 3, 0, 1, 1, Qt.AlignLeft)
- self.formatForm.addWidget(self.noStyling, 3, 1, 1, 2, Qt.AlignRight)
+ fontFamilyLabel = QLabel("Font family")
+ fontSizeLabel = QLabel("Font size")
+ lineHeightLabel = QLabel("Line height")
+ justifyLabel = QLabel("Justify text")
+ stylingLabel = QLabel("Disable styling")
- self.formatForm.setColumnStretch(0, 0)
- self.formatForm.setColumnStretch(1, 1)
- self.formatForm.setColumnStretch(2, 0)
+ self.fontForm.addWidget(fontFamilyLabel, 0, 0, 1, 1, Qt.AlignLeft)
+ self.fontForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight)
+ self.fontForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight)
+ self.fontForm.addWidget(fontSizeLabel, 1, 0, 1, 1, Qt.AlignLeft)
+ self.fontForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight)
+ self.fontForm.addWidget(lineHeightLabel, 2, 0, 1, 1, Qt.AlignLeft)
+ self.fontForm.addWidget(self.lineHeight, 2, 1, 1, 2, Qt.AlignRight)
- # Include Switches
- # ================
+ self.fontForm.setColumnStretch(0, 0)
+ self.fontForm.setColumnStretch(1, 1)
+ self.fontForm.setColumnStretch(2, 0)
- self.textGroup = QGroupBox("Text Options", self)
+ # Styling Options
+ # ===============
+
+ self.styleGroup = QGroupBox("Styling Options", self)
+ self.styleForm = QGridLayout(self)
+ self.styleGroup.setLayout(self.styleForm)
+
+ self.justifyText = QSwitch(width=wS, height=hS)
+ self.justifyText.setChecked(
+ self.optState.getBool("GuiBuildNovel", "justifyText", False)
+ )
+
+ self.noStyling = QSwitch(width=wS, height=hS)
+ self.noStyling.setChecked(
+ self.optState.getBool("GuiBuildNovel", "noStyling", False)
+ )
+
+ self.styleForm.addWidget(justifyLabel, 1, 0, 1, 1, Qt.AlignLeft)
+ self.styleForm.addWidget(self.justifyText, 1, 1, 1, 2, Qt.AlignRight)
+ self.styleForm.addWidget(stylingLabel, 2, 0, 1, 1, Qt.AlignLeft)
+ self.styleForm.addWidget(self.noStyling, 2, 1, 1, 2, Qt.AlignRight)
+
+ self.styleForm.setColumnStretch(0, 0)
+ self.styleForm.setColumnStretch(1, 1)
+
+ # Include Options
+ # ===============
+
+ self.textGroup = QGroupBox("Include Options", self)
self.textForm = QGridLayout(self)
self.textGroup.setLayout(self.textForm)
- self.includeSynopsis = QSwitch()
- self.includeSynopsis.setToolTip(
- "Include synopsis comments in the output."
- )
+ self.includeSynopsis = QSwitch(width=wS, height=hS)
self.includeSynopsis.setChecked(
self.optState.getBool("GuiBuildNovel", "incSynopsis", False)
)
- self.includeComments = QSwitch()
- self.includeComments.setToolTip(
- "Include plain comments in the output."
- )
+ self.includeComments = QSwitch(width=wS, height=hS)
self.includeComments.setChecked(
self.optState.getBool("GuiBuildNovel", "incComments", False)
)
- self.includeKeywords = QSwitch()
- self.includeKeywords.setToolTip(
- "Include meta keywords (tags, references) in the output."
- )
+ self.includeKeywords = QSwitch(width=wS, height=hS)
self.includeKeywords.setChecked(
self.optState.getBool("GuiBuildNovel", "incKeywords", False)
)
- self.includeBody = QSwitch()
- self.includeBody.setToolTip(
- "Include body text in the output."
- )
+ self.includeBody = QSwitch(width=wS, height=hS)
self.includeBody.setChecked(
self.optState.getBool("GuiBuildNovel", "incBodyText", True)
)
- self.textForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft)
- self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight)
- self.textForm.addWidget(QLabel("Include comments"), 1, 0, 1, 1, Qt.AlignLeft)
- self.textForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight)
- self.textForm.addWidget(QLabel("Include keywords"), 2, 0, 1, 1, Qt.AlignLeft)
- self.textForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight)
- self.textForm.addWidget(QLabel("Include body text"), 3, 0, 1, 1, Qt.AlignLeft)
- self.textForm.addWidget(self.includeBody, 3, 1, 1, 1, Qt.AlignRight)
+ synopsisLabel = QLabel("Include synopsis")
+ commentsLabel = QLabel("Include comments")
+ keywordsLabel = QLabel("Include keywords")
+ bodyLabel = QLabel("Include body text")
+
+ self.textForm.addWidget(synopsisLabel, 0, 0, 1, 1, Qt.AlignLeft)
+ self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight)
+ self.textForm.addWidget(commentsLabel, 1, 0, 1, 1, Qt.AlignLeft)
+ self.textForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight)
+ self.textForm.addWidget(keywordsLabel, 2, 0, 1, 1, Qt.AlignLeft)
+ self.textForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight)
+ self.textForm.addWidget(bodyLabel, 3, 0, 1, 1, Qt.AlignLeft)
+ self.textForm.addWidget(self.includeBody, 3, 1, 1, 1, Qt.AlignRight)
self.textForm.setColumnStretch(0, 1)
self.textForm.setColumnStretch(1, 0)
@@ -307,7 +331,7 @@ class GuiBuildNovel(QDialog):
self.fileForm = QGridLayout(self)
self.fileGroup.setLayout(self.fileForm)
- self.novelFiles = QSwitch()
+ self.novelFiles = QSwitch(width=wS, height=hS)
self.novelFiles.setToolTip(
"Include files with layouts 'Book', 'Page', 'Partition', "
"'Chapter', 'Unnumbered', and 'Scene'."
@@ -316,13 +340,13 @@ class GuiBuildNovel(QDialog):
self.optState.getBool("GuiBuildNovel", "addNovel", True)
)
- self.noteFiles = QSwitch()
+ self.noteFiles = QSwitch(width=wS, height=hS)
self.noteFiles.setToolTip("Include files with layout 'Note'.")
self.noteFiles.setChecked(
self.optState.getBool("GuiBuildNovel", "addNotes", False)
)
- self.ignoreFlag = QSwitch()
+ self.ignoreFlag = QSwitch(width=wS, height=hS)
self.ignoreFlag.setToolTip(
"Ignore the 'Include when building project' setting and include "
"all files in the output."
@@ -331,12 +355,16 @@ class GuiBuildNovel(QDialog):
self.optState.getBool("GuiBuildNovel", "ignoreFlag", False)
)
- self.fileForm.addWidget(QLabel("Include novel files"), 0, 0, 1, 1, Qt.AlignLeft)
- self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight)
- self.fileForm.addWidget(QLabel("Include note files"), 1, 0, 1, 1, Qt.AlignLeft)
- self.fileForm.addWidget(self.noteFiles, 1, 1, 1, 1, Qt.AlignRight)
- self.fileForm.addWidget(QLabel("Ignore export flag"), 2, 0, 1, 1, Qt.AlignLeft)
- self.fileForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight)
+ novelLabel = QLabel("Include novel files")
+ notesLabel = QLabel("Include note files")
+ exportLabel = QLabel("Ignore export flag")
+
+ self.fileForm.addWidget(novelLabel, 0, 0, 1, 1, Qt.AlignLeft)
+ self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight)
+ self.fileForm.addWidget(notesLabel, 1, 0, 1, 1, Qt.AlignLeft)
+ self.fileForm.addWidget(self.noteFiles, 1, 1, 1, 1, Qt.AlignRight)
+ self.fileForm.addWidget(exportLabel, 2, 0, 1, 1, Qt.AlignLeft)
+ self.fileForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight)
self.fileForm.setColumnStretch(0, 1)
self.fileForm.setColumnStretch(1, 0)
@@ -348,16 +376,23 @@ class GuiBuildNovel(QDialog):
self.exportForm = QGridLayout(self)
self.exportGroup.setLayout(self.exportForm)
- self.replaceTabs = QSwitch()
- self.replaceTabs.setToolTip(
- "Replace all tabs with eight spaces."
- )
+ self.replaceTabs = QSwitch(width=wS, height=hS)
self.replaceTabs.setChecked(
self.optState.getBool("GuiBuildNovel", "replaceTabs", False)
)
- self.exportForm.addWidget(QLabel("Replace tabs with spaces"), 0, 0, 1, 1, Qt.AlignLeft)
- self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight)
+ self.replaceUCode = QSwitch(width=wS, height=hS)
+ self.replaceUCode.setChecked(
+ self.optState.getBool("GuiBuildNovel", "replaceUCode", False)
+ )
+
+ tabsLabel = QLabel("Replace tabs with spaces")
+ uCodeLabel = QLabel("Replace Unicode in HTML")
+
+ self.exportForm.addWidget(tabsLabel, 0, 0, 1, 1, Qt.AlignLeft)
+ self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight)
+ self.exportForm.addWidget(uCodeLabel, 1, 0, 1, 1, Qt.AlignLeft)
+ self.exportForm.addWidget(self.replaceUCode, 1, 1, 1, 1, Qt.AlignRight)
self.exportForm.setColumnStretch(0, 1)
self.exportForm.setColumnStretch(1, 0)
@@ -447,7 +482,8 @@ class GuiBuildNovel(QDialog):
# The Tool Box
self.toolsBox = QVBoxLayout()
self.toolsBox.addWidget(self.titleGroup)
- self.toolsBox.addWidget(self.formatGroup)
+ self.toolsBox.addWidget(self.fontGroup)
+ self.toolsBox.addWidget(self.styleGroup)
self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.fileGroup)
self.toolsBox.addWidget(self.exportGroup)
@@ -607,6 +643,7 @@ class GuiBuildNovel(QDialog):
fmtSection = self.fmtSection.text().strip()
textFont = self.textFont.text()
textSize = self.textSize.value()
+ lineHeight = self.lineHeight.value()
justifyText = self.justifyText.isChecked()
noStyling = self.noStyling.isChecked()
incSynopsis = self.includeSynopsis.isChecked()
@@ -616,6 +653,7 @@ class GuiBuildNovel(QDialog):
noteFiles = self.noteFiles.isChecked()
ignoreFlag = self.ignoreFlag.isChecked()
includeBody = self.includeBody.isChecked()
+ replaceUCode = self.replaceUCode.isChecked()
# Get font information
fontInfo = QFontInfo(QFont(textFont, textSize))
@@ -632,6 +670,7 @@ class GuiBuildNovel(QDialog):
bldObj.setFont(textFont, textSize, textFixed)
bldObj.setJustify(justifyText)
+ bldObj.setLineHeight(lineHeight)
bldObj.setSynopsis(incSynopsis)
bldObj.setComments(incComments)
@@ -640,6 +679,7 @@ class GuiBuildNovel(QDialog):
if isHtml:
bldObj.setStyles(not noStyling)
+ bldObj.setReplaceUnicode(replaceUCode)
if isOdt:
bldObj.setColourHeaders(not noStyling)
@@ -668,7 +708,7 @@ class GuiBuildNovel(QDialog):
elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
bldObj.setText(tItem.itemHandle)
- bldObj.doAutoReplace()
+ bldObj.doPreProcessing()
bldObj.tokenizeText()
bldObj.doHeaders()
if doConvert:
@@ -1012,14 +1052,14 @@ class GuiBuildNovel(QDialog):
nw.logException()
return False
- if "htmlText" in theData.keys():
- self.htmlText = theData["htmlText"]
- dataCount += 1
+ if "buildTime" in theData.keys():
+ self.buildTime = theData["buildTime"]
if "htmlStyle" in theData.keys():
self.htmlStyle = theData["htmlStyle"]
dataCount += 1
- if "buildTime" in theData.keys():
- self.buildTime = theData["buildTime"]
+ if "htmlText" in theData.keys():
+ self.htmlText = theData["htmlText"]
+ dataCount += 1
return dataCount == 2
@@ -1032,9 +1072,9 @@ class GuiBuildNovel(QDialog):
try:
with open(buildCache, mode="w+", encoding="utf8") as outFile:
outFile.write(json.dumps({
- "htmlText" : self.htmlText,
- "htmlStyle" : self.htmlStyle,
"buildTime" : self.buildTime,
+ "htmlStyle" : self.htmlStyle,
+ "htmlText" : self.htmlText,
}, indent=2))
except Exception:
logger.error("Failed to save build cache")
@@ -1079,46 +1119,47 @@ class GuiBuildNovel(QDialog):
"section" : self.fmtSection.text().strip(),
})
- winWidth = self.mainConf.rpxInt(self.width())
- winHeight = self.mainConf.rpxInt(self.height())
- justifyText = self.justifyText.isChecked()
- noStyling = self.noStyling.isChecked()
- textFont = self.textFont.text()
- textSize = self.textSize.value()
- novelFiles = self.novelFiles.isChecked()
- noteFiles = self.noteFiles.isChecked()
- ignoreFlag = self.ignoreFlag.isChecked()
- incSynopsis = self.includeSynopsis.isChecked()
- incComments = self.includeComments.isChecked()
- incKeywords = self.includeKeywords.isChecked()
- incBodyText = self.includeBody.isChecked()
- replaceTabs = self.replaceTabs.isChecked()
+ winWidth = self.mainConf.rpxInt(self.width())
+ winHeight = self.mainConf.rpxInt(self.height())
+ justifyText = self.justifyText.isChecked()
+ noStyling = self.noStyling.isChecked()
+ textFont = self.textFont.text()
+ textSize = self.textSize.value()
+ lineHeight = self.lineHeight.value()
+ novelFiles = self.novelFiles.isChecked()
+ noteFiles = self.noteFiles.isChecked()
+ ignoreFlag = self.ignoreFlag.isChecked()
+ incSynopsis = self.includeSynopsis.isChecked()
+ incComments = self.includeComments.isChecked()
+ incKeywords = self.includeKeywords.isChecked()
+ incBodyText = self.includeBody.isChecked()
+ replaceTabs = self.replaceTabs.isChecked()
+ replaceUCode = self.replaceUCode.isChecked()
mainSplit = self.mainSplit.sizes()
- if len(mainSplit) == 2:
- boxWidth = self.mainConf.rpxInt(mainSplit[0])
- docWidth = self.mainConf.rpxInt(mainSplit[1])
- else:
- boxWidth = 100
- docWidth = 100
+ boxWidth = self.mainConf.rpxInt(mainSplit[0])
+ docWidth = self.mainConf.rpxInt(mainSplit[1])
# GUI Settings
- self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
- self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
- self.optState.setValue("GuiBuildNovel", "boxWidth", boxWidth)
- self.optState.setValue("GuiBuildNovel", "docWidth", docWidth)
- self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
- self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
- self.optState.setValue("GuiBuildNovel", "textFont", textFont)
- self.optState.setValue("GuiBuildNovel", "textSize", textSize)
- self.optState.setValue("GuiBuildNovel", "addNovel", novelFiles)
- self.optState.setValue("GuiBuildNovel", "addNotes", noteFiles)
- self.optState.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
- self.optState.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
- self.optState.setValue("GuiBuildNovel", "incComments", incComments)
- self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
- self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
- self.optState.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
+ self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
+ self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
+ self.optState.setValue("GuiBuildNovel", "boxWidth", boxWidth)
+ self.optState.setValue("GuiBuildNovel", "docWidth", docWidth)
+ self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
+ self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
+ self.optState.setValue("GuiBuildNovel", "textFont", textFont)
+ self.optState.setValue("GuiBuildNovel", "textSize", textSize)
+ self.optState.setValue("GuiBuildNovel", "lineHeight", lineHeight)
+ self.optState.setValue("GuiBuildNovel", "addNovel", novelFiles)
+ self.optState.setValue("GuiBuildNovel", "addNotes", noteFiles)
+ self.optState.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
+ self.optState.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
+ self.optState.setValue("GuiBuildNovel", "incComments", incComments)
+ self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
+ self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
+ self.optState.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
+ self.optState.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
+
self.optState.saveSettings()
return
diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py
index 24d53019..ddc0d876 100644
--- a/nw/gui/docviewer.py
+++ b/nw/gui/docviewer.py
@@ -176,7 +176,7 @@ class GuiDocViewer(QTextBrowser):
# See issue #298
try:
aDoc.setText(tHandle)
- aDoc.doAutoReplace()
+ aDoc.doPreProcessing()
aDoc.tokenizeText()
aDoc.doConvert()
aDoc.doPostProcessing()
diff --git a/tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.htm b/tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.htm
index 413f48df..5fa77a9b 100644
--- a/tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.htm
+++ b/tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.htm
@@ -5,17 +5,17 @@
Lorem Ipsum