From 4dead91af26f6d7d046f7d199d96c1ad60fdc534 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 11 May 2020 18:18:19 +0200 Subject: [PATCH] Extended the style formatting of tokenizer and tohtml to allow multiple styles for a block --- nw/core/tohtml.py | 36 +++++--- nw/core/tokenizer.py | 204 ++++++++++++++++++++++++++++++++++++------- nw/gui/build.py | 43 ++++----- 3 files changed, 219 insertions(+), 64 deletions(-) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 2b98be99..ec3cec22 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -102,19 +102,35 @@ class ToHtml(Tokenizer): self.theResult = "" thisPar = [] - for tType, tText, tFormat, tAlign in self.theTokens: + for tType, tText, tFormat, tStyle in self.theTokens: # Styles aStyle = [] - if tAlign == self.A_CENTRE: - aStyle.append("text-align: center;") - elif tAlign == self.A_RIGHT: - aStyle.append("text-align: right;") - elif tAlign == self.A_JUSTIFY: - aStyle.append("text-align: justify;") - - if tType == self.T_HEAD2: - aStyle.append("page-break-before: always;") + if tStyle is not None: + if tStyle & self.A_LEFT: + aStyle.append("text-align: left;") + if tStyle & self.A_RIGHT: + aStyle.append("text-align: right;") + if tStyle & self.A_CENTRE: + aStyle.append("text-align: center;") + if tStyle & self.A_JUSTIFY: + aStyle.append("text-align: justify;") + if tStyle & self.A_PBB: + aStyle.append("page-break-before: always;") + if tStyle & self.A_PBB_L: + aStyle.append("page-break-before: left;") + if tStyle & self.A_PBB_R: + aStyle.append("page-break-before: right;") + if tStyle & self.A_PBB_AV: + aStyle.append("page-break-before: avoid;") + if tStyle & self.A_PBA: + aStyle.append("page-break-after: always;") + if tStyle & self.A_PBA_L: + aStyle.append("page-break-after: left;") + if tStyle & self.A_PBA_R: + aStyle.append("page-break-after: right;") + if tStyle & self.A_PBA_AV: + aStyle.append("page-break-after: avoid;") if len(aStyle) > 0: hStyle = " style='%s'" % (" ".join(aStyle)) diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index a39d12f2..84fbb100 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -60,10 +60,18 @@ class Tokenizer(): T_SKIP = 11 # Paragraph break T_PBREAK = 12 # Page break - A_LEFT = 1 # Left aligned - A_RIGHT = 2 # Right aligned - A_CENTRE = 3 # Centred - A_JUSTIFY = 4 # Justified + A_LEFT = 1 # Left aligned + A_RIGHT = 2 # Right aligned + A_CENTRE = 4 # Centred + A_JUSTIFY = 8 # Justified + A_PBB = 16 # Page break before + A_PBB_L = 32 # Page break before, left + A_PBB_R = 64 # Page break before, right + A_PBB_AV = 128 # Page break, avoid + A_PBA = 256 # Page break after + A_PBA_L = 512 # Page break after, left + A_PBA_R = 1024 # Page break after, right + A_PBA_AV = 2048 # Page break, avoid def __init__(self, theProject, theParent): @@ -208,6 +216,13 @@ class Tokenizer(): just contains plain text. in the case of plain text, apply the same RegExes that the syntax highlighter uses and save the locations of these formatting tags into the token array. + + The format of the token list is an entry with a four-tuple for + each line in the file. The tuple is as follows: + 1: The type of the block, self.T_* + 2: The text content of the block, without leading tags + 3: The internal formatting map of the text, self.FMT_* + 4: The style of the block, self.A_* """ # RegExes for adding formatting tags within text lines @@ -233,23 +248,63 @@ class Tokenizer(): # Tag lines starting with specific characters if len(aLine.strip()) == 0: - self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT)) + self.theTokens.append(( + self.T_EMPTY, + "", + None, + None + )) elif aLine[0] == "%": cLine = aLine[1:].strip() if cLine.lower().startswith("synopsis:"): - self.theTokens.append((self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign)) + self.theTokens.append(( + self.T_SYNOPSIS, + cLine[9:].strip(), + None, + defAlign + )) else: - self.theTokens.append((self.T_COMMENT, aLine[1:].strip(), None, defAlign)) + self.theTokens.append(( + self.T_COMMENT, + aLine[1:].strip(), + None, + defAlign + )) elif aLine[0] == "@": - self.theTokens.append((self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT)) + self.theTokens.append(( + self.T_KEYWORD, + aLine[1:].strip(), + None, + self.A_LEFT + )) elif aLine[:2] == "# ": - self.theTokens.append((self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT)) + self.theTokens.append(( + self.T_HEAD1, + aLine[2:].strip(), + None, + self.A_LEFT | self.A_PBB + )) elif aLine[:3] == "## ": - self.theTokens.append((self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT)) + self.theTokens.append(( + self.T_HEAD2, + aLine[3:].strip(), + None, + self.A_LEFT | self.A_PBA_AV + )) elif aLine[:4] == "### ": - self.theTokens.append((self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT)) + self.theTokens.append(( + self.T_HEAD3, + aLine[4:].strip(), + None, + self.A_LEFT | self.A_PBA_AV + )) elif aLine[:5] == "#### ": - self.theTokens.append((self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT)) + self.theTokens.append(( + self.T_HEAD4, + aLine[5:].strip(), + None, + self.A_LEFT | self.A_PBA_AV + )) else: if not self.doBodyText: # Skip all body text @@ -270,10 +325,20 @@ class Tokenizer(): # Save the line as is, but append the array of formatting locations # sorted by position fmtPos = sorted(fmtPos, key=itemgetter(0)) - self.theTokens.append((self.T_TEXT, aLine, fmtPos, defAlign)) + self.theTokens.append(( + self.T_TEXT, + aLine, + fmtPos, + defAlign + )) # Always add an empty line at the end - self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT)) + self.theTokens.append(( + self.T_EMPTY, + "", + None, + None + )) return @@ -308,53 +373,126 @@ class Tokenizer(): if tType == self.T_TEXT: self.firstScene = False - elif tType == self.T_HEAD2: + elif tType == self.T_HEAD2: # Novel Chapter if not isUnNum: self.numChapter += 1 tText = self._formatChapter(tText,isUnNum) - self.theTokens[n] = (tType, tText, None, self.A_LEFT) + self.theTokens[n] = ( + tType, + tText, + None, + self.A_LEFT | self.A_PBB_R + ) self.firstScene = True - elif tType == self.T_HEAD3: + elif tType == self.T_HEAD3: # Novel Scene tTemp = self._formatScene(tText) if tTemp == "" and self.hideScene: - self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT) + self.theTokens[n] = ( + self.T_EMPTY, + "", + None, + None + ) elif tTemp == "" and not self.hideScene: if self.firstScene: - self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT) + self.theTokens[n] = ( + self.T_EMPTY, + "", + None, + None + ) else: - self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT) + self.theTokens[n] = ( + self.T_SKIP, + "", + None, + None + ) elif tTemp == self.fmtScene: if self.firstScene: - self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT) + self.theTokens[n] = ( + self.T_EMPTY, + "", + None, + None + ) else: - self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE) + self.theTokens[n] = ( + self.T_SEP, + tTemp, + None, + self.A_CENTRE + ) else: - self.theTokens[n] = (tType, tTemp, None, self.A_LEFT) + self.theTokens[n] = ( + tType, + tTemp, + None, + self.A_LEFT | self.A_PBA_AV + ) self.firstScene = False - elif tType == self.T_HEAD4: + elif tType == self.T_HEAD4: # Novel Section tTemp = self._formatSection(tText) if tTemp == "" and self.hideSection: - self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT) + self.theTokens[n] = ( + self.T_EMPTY, + "", + None, + None + ) elif tTemp == "" and not self.hideSection: - self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT) + self.theTokens[n] = ( + self.T_SKIP, + "", + None, + None + ) elif tTemp == self.fmtSection: - self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE) + self.theTokens[n] = ( + self.T_SEP, + tTemp, + None, + self.A_CENTRE + ) else: - self.theTokens[n] = (tType, tTemp, None, self.A_LEFT) + self.theTokens[n] = ( + tType, + tTemp, + None, + self.A_LEFT | self.A_PBA_AV + ) - # For title page and partitions, we need to centre all text - # and for some formats, we need a page break + # For title page and partitions, we need to centre all text. + # For partition, we also add a page break before, and for + # both types we always add a page break after the content. if isTitle or isPart: for n in range(len(self.theTokens)): tToken = self.theTokens[n] tType = tToken[0] tText = tToken[1] tFormat = tToken[2] - self.theTokens[n] = (tType, tText, tFormat, self.A_CENTRE) - - self.theTokens.append((self.T_PBREAK, "", None, self.A_LEFT)) + if isTitle: + self.theTokens[n] = ( + tType, + tText, + tFormat, + self.A_CENTRE + ) + else: + self.theTokens[n] = ( + tType, + tText, + tFormat, + self.A_CENTRE | self.A_PBB_R + ) + self.theTokens.append(( + self.T_PBREAK, + "", + None, + None + )) return diff --git a/nw/gui/build.py b/nw/gui/build.py index 455c7ec7..0cca7332 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -129,21 +129,6 @@ class GuiBuildNovel(QDialog): self.titleForm.setColumnStretch(0, 1) self.titleForm.setColumnStretch(1, 0) - # Text Options - # ============= - self.textGroup = QGroupBox("Text Options", self) - self.textForm = QGridLayout(self) - self.textGroup.setLayout(self.textForm) - - self.justifyText = QSwitch() - self.justifyText.setChecked(self.optState.getBool("GuiBuildNovel", "justifyText", False)) - - self.textForm.addWidget(QLabel("Justify text"), 0, 0) - self.textForm.addWidget(self.justifyText, 0, 1) - - self.textForm.setColumnStretch(0, 1) - self.textForm.setColumnStretch(1, 0) - # Build Settings # ============== self.buildGroup = QGroupBox("Build Overrides", self) @@ -159,6 +144,21 @@ class GuiBuildNovel(QDialog): self.buildForm.setColumnStretch(0, 1) self.buildForm.setColumnStretch(1, 0) + # Text Options + # ============= + self.textGroup = QGroupBox("Text Options", self) + self.textForm = QGridLayout(self) + self.textGroup.setLayout(self.textForm) + + self.justifyText = QSwitch() + self.justifyText.setChecked(self.optState.getBool("GuiBuildNovel", "justifyText", False)) + + self.textForm.addWidget(QLabel("Justify text"), 0, 0) + self.textForm.addWidget(self.justifyText, 0, 1) + + self.textForm.setColumnStretch(0, 1) + self.textForm.setColumnStretch(1, 0) + # Include Switches # ================ self.includeGroup = QGroupBox("Include Non-Text Elements", self) @@ -230,7 +230,7 @@ class GuiBuildNovel(QDialog): self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT)) self.saveMenu.addAction(self.saveODT) - # self.savePDF = QAction("Portable Document (.pdf)") + # self.savePDF = QAction("Portable Document Format (.pdf)") # self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF)) # self.saveMenu.addAction(self.savePDF) @@ -238,13 +238,14 @@ class GuiBuildNovel(QDialog): self.saveHTM1.triggered.connect(lambda: self._saveDocument(self.FMT_HTM1)) self.saveMenu.addAction(self.saveHTM1) - self.saveHTM2 = QAction("Plain HTML (.htm)") + self.saveHTM2 = QAction("%s HTML (.htm)" % nw.__package__) self.saveHTM2.triggered.connect(lambda: self._saveDocument(self.FMT_HTM2)) self.saveMenu.addAction(self.saveHTM2) - # self.saveMD = QAction("Markdown (.md)") - # self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) - # self.saveMenu.addAction(self.saveMD) + if self.mainConf.verQtValue >= 51400: + self.saveMD = QAction("Markdown (.md)") + self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) + self.saveMenu.addAction(self.saveMD) self.saveTXT = QAction("Plain Text (.txt)") self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT)) @@ -261,8 +262,8 @@ class GuiBuildNovel(QDialog): # Assemble GUI # ============ self.toolsBox.addWidget(self.titleGroup) - self.toolsBox.addWidget(self.textGroup) self.toolsBox.addWidget(self.buildGroup) + self.toolsBox.addWidget(self.textGroup) self.toolsBox.addWidget(self.includeGroup) self.toolsBox.addWidget(self.addsGroup) self.toolsBox.addStretch(1)