From a5729cf0a9a8bda6544e8f1254cb1e016a39556a Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 May 2020 17:59:07 +0200 Subject: [PATCH] Cleaned up html export --- nw/core/tohtml.py | 141 +++++++++++++++++++++++++++++++------------ nw/core/tokenizer.py | 82 +++++++++++-------------- nw/gui/build.py | 57 +++++++++-------- 3 files changed, 166 insertions(+), 114 deletions(-) diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 12d34404..0fb517b8 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -48,20 +48,16 @@ class ToHtml(Tokenizer): "<" : "<", ">" : ">", "&" : "&", - "\t" : " ", + "\t" : " "*2, nwUnicode.U_ENDASH : nwUnicode.H_ENDASH, nwUnicode.U_EMDASH : nwUnicode.H_EMDASH, nwUnicode.U_HELLIP : nwUnicode.H_HELLIP, nwUnicode.U_NBSP : nwUnicode.H_NBSP, } - 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 - ) + self.revDict = {} + self.reReplace = [] + self.reReverse = [] + self._buildRegEx() return @@ -79,6 +75,7 @@ class ToHtml(Tokenizer): self.doKeywords = True self.doComments = doComments self.repDict["\t"] = " "*8 + self._buildRegEx() return ## @@ -120,40 +117,61 @@ class ToHtml(Tokenizer): self.FMT_U_E : "", } + if self.isNovel and self.genMode != self.M_PREVIEW: + # For novel files for export, we bump the titles one level + # up, as this is more useful for printing and word processor + # imports. + h1 = "h1 class=\"title\"" + h2 = "h1" + h3 = "h2" + h4 = "h3" + else: + h1 = "h1" + h2 = "h2" + h3 = "h3" + h4 = "h4" + + alignHead = self.A_LEFT + if self.doJustify: + alignPar = self.A_JUSTIFY + else: + alignPar = self.A_LEFT + self.theResult = "" thisPar = [] parStyle = None tmpResult = [] + hasHardBreak = False for tType, tText, tFormat, tStyle in self.theTokens: # Styles aStyle = [] 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_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 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)) @@ -164,33 +182,42 @@ class ToHtml(Tokenizer): if tType == self.T_EMPTY: if parStyle is None: parStyle = "" + if hasHardBreak: + parClass = " class='break'" + else: + parClass = "" if len(thisPar) > 0: tTemp = "".join(thisPar) - tmpResult.append("%s

\n" % (parStyle, tTemp.rstrip())) + tmpResult.append("%s

\n" % (parStyle, parClass, tTemp.rstrip())) thisPar = [] parStyle = None + hasHardBreak = False + + elif tType == self.T_TITLE: + tHead = tText.replace(r"\\", "
") + tmpResult.append("
%s
\n" % (hStyle, tHead)) elif tType == self.T_HEAD1: tHead = tText.replace(r"\\", "
") - tmpResult.append("%s\n" % (hStyle, tHead)) + tmpResult.append("<%s%s>%s\n" % (h1, hStyle, tHead, h1)) elif tType == self.T_HEAD2: tHead = tText.replace(r"\\", "
") - tmpResult.append("%s\n" % (hStyle, tHead)) + tmpResult.append("<%s%s>%s\n" % (h2, hStyle, tHead, h2)) elif tType == self.T_HEAD3: tHead = tText.replace(r"\\", "
") - tmpResult.append("%s\n" % (hStyle, tHead)) + tmpResult.append("<%s%s>%s\n" % (h3, hStyle, tHead, h3)) elif tType == self.T_HEAD4: tHead = tText.replace(r"\\", "
") - tmpResult.append("%s\n" % (hStyle, tHead)) + tmpResult.append("<%s%s>%s\n" % (h4, hStyle, tHead, h4)) elif tType == self.T_SEP: - tmpResult.append("%s

\n" % (hStyle, tText)) + tmpResult.append("

%s

\n" % tText) elif tType == self.T_SKIP: - tmpResult.append(" 

\n" % hStyle) + tmpResult.append("

 

\n") elif tType == self.T_TEXT: tTemp = tText @@ -200,6 +227,7 @@ class ToHtml(Tokenizer): tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:] if tText.endswith(" "): thisPar.append(tTemp.rstrip()+"
") + hasHardBreak = True else: thisPar.append(tTemp.rstrip()+" ") @@ -217,6 +245,30 @@ class ToHtml(Tokenizer): return + def getStylesheet(self): + """Generate a stylesheet appropriate for the current settings. + """ + theStyles = [] + + if self.doJustify: + theStyles.append(r"p {text-align: justify;}") + else: + theStyles.append(r"p {text-align: left;}") + + theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}") + theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}") + theStyles.append(r"h1, h2, h3, h4 {page-break-after: avoid;}") + theStyles.append(r"h1 {page-break-before: always;}") + theStyles.append(r".title {font-size: 2.5em; font-weight: bold; page-break-before: never;}") + theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}") + theStyles.append(r".break {text-align: left;}") + theStyles.append(r".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}") + theStyles.append(r".skip {margin-top: 1em; margin-bottom: 1em;}") + theStyles.append(r".synopsis {font-style: italic;}") + theStyles.append(r".comment {font-style: italic; color: rgb(100, 100, 100);}") + + return theStyles + ## # Internal Functions ## @@ -240,7 +292,6 @@ class ToHtml(Tokenizer): def _formatKeywords(self, tText): """Apply HTML formatting to keywords. """ - tText = "@"+tText isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText) if not isValid or not theBits: @@ -270,4 +321,16 @@ class ToHtml(Tokenizer): return "
%s
" % 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 a3c04bd8..8773cdb1 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -51,26 +51,23 @@ class Tokenizer(): T_SYNOPSIS = 2 # Synopsis comment T_COMMENT = 3 # Comment line T_KEYWORD = 4 # Command line - T_HEAD1 = 5 # Header 1 (title) - T_HEAD2 = 6 # Header 2 (chapter) - T_HEAD3 = 7 # Header 3 (scene) - T_HEAD4 = 8 # Header 4 - T_TEXT = 9 # Text line - T_SEP = 10 # Scene separator - T_SKIP = 11 # Paragraph break + T_TITLE = 5 # Title + T_HEAD1 = 6 # Header 1 + T_HEAD2 = 7 # Header 2 + T_HEAD3 = 8 # Header 3 + T_HEAD4 = 9 # Header 4 + T_TEXT = 10 # Text line + T_SEP = 11 # Scene separator + T_SKIP = 12 # Paragraph break - 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 + 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_AV = 32 # Page break, avoid + A_PBA = 64 # Page break after + A_PBA_AV = 128 # Page break, avoid def __init__(self, theProject, theParent): @@ -283,11 +280,6 @@ class Tokenizer(): [None, self.FMT_U_B, None, self.FMT_U_E] )] - if self.doJustify: - defAlign = self.A_JUSTIFY - else: - defAlign = self.A_LEFT - self.theTokens = [] self.theMarkdown = "" tmpMarkdown = [] @@ -304,45 +296,45 @@ class Tokenizer(): cLine = aLine[1:].strip() if cLine.lower().startswith("synopsis:"): self.theTokens.append(( - self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign + self.T_SYNOPSIS, cLine[9:].strip(), None, None )) if self.doSynopsis: tmpMarkdown.append("%s\n" % aLine) else: self.theTokens.append(( - self.T_COMMENT, aLine[1:].strip(), None, defAlign + self.T_COMMENT, aLine[1:].strip(), None, None )) if self.doComments: tmpMarkdown.append("%s\n" % aLine) elif aLine[0] == "@": self.theTokens.append(( - self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT + self.T_KEYWORD, aLine[1:].strip(), None, None )) if self.doKeywords: tmpMarkdown.append("%s\n" % aLine) elif aLine[:2] == "# ": self.theTokens.append(( - self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT | self.A_PBB + self.T_HEAD1, aLine[2:].strip(), None, None )) tmpMarkdown.append("%s\n" % aLine) elif aLine[:3] == "## ": self.theTokens.append(( - self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT | self.A_PBA_AV + self.T_HEAD2, aLine[3:].strip(), None, None )) tmpMarkdown.append("%s\n" % aLine) elif aLine[:4] == "### ": self.theTokens.append(( - self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT | self.A_PBA_AV + self.T_HEAD3, aLine[4:].strip(), None, None )) tmpMarkdown.append("%s\n" % aLine) elif aLine[:5] == "#### ": self.theTokens.append(( - self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT | self.A_PBA_AV + self.T_HEAD4, aLine[5:].strip(), None, None )) tmpMarkdown.append("%s\n" % aLine) @@ -366,14 +358,9 @@ class Tokenizer(): # Save the line as is, but append the array of formatting locations # sorted by position fmtPos = sorted(fmtPos, key=itemgetter(0)) - if aLine.endswith(" "): - self.theTokens.append(( - self.T_TEXT, aLine, fmtPos, self.A_LEFT - )) - else: - self.theTokens.append(( - self.T_TEXT, aLine, fmtPos, defAlign - )) + self.theTokens.append(( + self.T_TEXT, aLine, fmtPos, None + )) tmpMarkdown.append("%s\n" % aLine) # Always add an empty line at the end @@ -415,7 +402,7 @@ class Tokenizer(): tText = self._formatHeading(self.fmtTitle, tText) self.theTokens[n] = ( - tType, tText, None, self.A_LEFT | self.A_PBB_R + tType, tText, None, None ) elif tType == self.T_HEAD2: @@ -431,7 +418,7 @@ class Tokenizer(): # Format the chapter header self.theTokens[n] = ( - tType, tText, None, self.A_LEFT | self.A_PBB_R + tType, tText, None, None ) # Set scene variables @@ -470,7 +457,7 @@ class Tokenizer(): ) else: self.theTokens[n] = ( - tType, tTemp, None, self.A_LEFT | self.A_PBA_AV + tType, tTemp, None, None ) # Definitely no longer the first scene @@ -495,21 +482,24 @@ class Tokenizer(): ) else: self.theTokens[n] = ( - tType, tTemp, None, self.A_LEFT | self.A_PBA_AV + tType, tTemp, None, None ) # 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. + # We also swap header level 1 with a title type instead. if self.isTitle or self.isPart: for n, tToken in enumerate(self.theTokens): tType = tToken[0] tText = tToken[1] tFormat = tToken[2] if self.isTitle: - self.theTokens[n] = ( - tType, tText, tFormat, self.A_CENTRE - ) + if tType == self.T_HEAD1: + tType = self.T_TITLE + self.theTokens[n] = ( + tType, tText, tFormat, self.A_CENTRE + ) # Add a page break after the last entry n = len(self.theTokens) - 1 diff --git a/nw/gui/build.py b/nw/gui/build.py index 737a13c4..8baf00fc 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -70,6 +70,7 @@ class GuiBuildNovel(QDialog): self.optState = self.theProject.optState self.htmlText = [] # List of html document + self.htmlStyle = [] # List of html styles self.nwdText = [] # List of markdown documents self.textLayout = [] # List of nwItemLayout entries @@ -320,6 +321,7 @@ class GuiBuildNovel(QDialog): tStart = time() self.htmlText = [] + self.htmlStyle = [] self.nwdText = [] self.textLayout = [] @@ -340,9 +342,11 @@ class GuiBuildNovel(QDialog): tEnd = time() logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart))) + self.htmlStyle = makeHtml.getStylesheet() # Load the preview document with the html data - self.docView.setHtml("".join(self.htmlText)) + self.docView.setStyleSheet(self.htmlStyle) + self.docView.setContent(self.htmlText) return @@ -484,6 +488,9 @@ class GuiBuildNovel(QDialog): outFile.write("\n") outFile.write("\n") outFile.write("\n") + outFile.write("\n") outFile.write("\n") outFile.write("
\n") for aLine in self.htmlText: @@ -612,7 +619,8 @@ class GuiBuildNovel(QDialog): if path.isfile(docPath): with open(docPath, mode="r", encoding="utf8") as inFile: helpText = inFile.read() - self.docView.setText(helpText) + self.docView.setStyleSheet() + self.docView.setContent(helpText) else: self.theParent.makeAlert( "Could not open help text file for Build Project.", nwAlert.ERROR @@ -648,7 +656,7 @@ class GuiBuildNovelDocView(QTextBrowser): docPalette.setColor(QPalette.Text, QColor( 0, 0, 0)) self.setPalette(docPalette) - self._makeStyleSheet() + self.setStyleSheet() self.show() @@ -656,35 +664,26 @@ class GuiBuildNovelDocView(QTextBrowser): return - def setText(self, theText): - self.setHtml(theText) + def setContent(self, theText): + """Set the content, either from text or list of text. + """ + if isinstance(theText, str): + self.setHtml(theText) + else: + self.setHtml("".join(theText)) return - ## - # Internal Functions - ## + def setStyleSheet(self, theStyles=[]): + """Set the stylesheet for the preview document. + """ + if not theStyles: + theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}") + theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}") + theStyles.append(r"a {color: rgb(137, 89, 168);}") + theStyles.append(r"mark {background-color: rgb(240, 198, 116);}") + theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}") - def _makeStyleSheet(self): - - styleSheet = ( - "h1, h2 {" - " color: rgb(66, 113, 174);" - "}\n" - "h3, h4 {" - " color: rgb(50, 50, 50);" - "}\n" - "a {" - " color: rgb(137, 89, 168);" - "}\n" - "mark {" - " background-color: rgb(240, 198, 116);" - "}\n" - ".tags {" - " color: rgb(245, 135, 31);" - " font-wright: bold;" - "}\n" - ) - self.qDocument.setDefaultStyleSheet(styleSheet) + self.qDocument.setDefaultStyleSheet("\n".join(theStyles)) return