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