diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index ab7e0c81..980ac6e6 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -61,6 +61,8 @@ class ToHtml(Tokenizer): self.reReverse = [] self._buildRegEx() + self.fullHTML = [] + return ## @@ -90,6 +92,11 @@ class ToHtml(Tokenizer): # Class Methods ## + def getFullResultSize(self): + """Return the size of the full HTML result. + """ + return sum([len(x) for x in self.fullHTML]) + def doAutoReplace(self): """Extend the auto-replace to also properly encode some unicode characters into their respective HTML entities. @@ -108,9 +115,12 @@ class ToHtml(Tokenizer): if self.genMode == self.M_PREVIEW: # Doesn't matter for preview as we don't use the markdown return - self.theMarkdown = self.reReverse.sub( - lambda x: self.revDict[x.group(0)], self.theMarkdown - ) + + if self.keepMarkdown: + self.theMarkdown[-1] = self.reReverse.sub( + lambda x: self.revDict[x.group(0)], self.theMarkdown[-1] + ) + return def doConvert(self): @@ -165,21 +175,28 @@ class ToHtml(Tokenizer): if tStyle is not None and self.cssStyles: if tStyle & self.A_LEFT: aStyle.append("text-align: left;") - if tStyle & self.A_RIGHT: + elif tStyle & self.A_RIGHT: aStyle.append("text-align: right;") - if tStyle & self.A_CENTRE: + elif tStyle & self.A_CENTRE: aStyle.append("text-align: center;") - if tStyle & self.A_JUSTIFY: + elif 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_AUT: + elif tStyle & self.A_PBB_AUT: aStyle.append("page-break-before: auto;") + if tStyle & self.A_PBA: aStyle.append("page-break-after: always;") - if tStyle & self.A_PBA_AUT: + elif tStyle & self.A_PBA_AUT: aStyle.append("page-break-after: auto;") + if tStyle & self.A_Z_BTMMRG: + aStyle.append("margin-bottom: 0;") + if tStyle & self.A_Z_TOPMRG: + aStyle.append("margin-top: 0;") + if len(aStyle) > 0: hStyle = " style='%s'" % (" ".join(aStyle)) else: @@ -255,6 +272,54 @@ class ToHtml(Tokenizer): self.theResult = "".join(tmpResult) tmpResult = [] + if self.genMode != self.M_PREVIEW: + self.fullHTML.append(self.theResult) + + return + + def saveHTML5(self, savePath): + """Save the data to an .html file. + """ + with open(savePath, mode="w", encoding="utf8") as outFile: + theStyle = self.getStyleSheet() + theStyle.append("article {width: 800px; margin: 40px auto;}") + bodyText = "".join(self.fullHTML) + bodyText = bodyText.replace("\t", " ") + + theHtml = ( + "\n" + "\n" + "\n" + "\n" + "{projTitle:s}\n" + "\n" + "\n" + "\n" + "
\n" + "{bodyText:s}\n" + "
\n" + "\n" + "\n" + ).format( + projTitle = self.theProject.projName, + htmlStyle = "\n".join(theStyle), + bodyText = bodyText, + ) + outFile.write(theHtml) + + return + + def replaceTabs(self, nSpaces=8, spaceChar=" "): + """Replace tabs with spaces in the html. + """ + htmlText = [] + eightSpace = spaceChar*nSpaces + for aLine in self.fullHTML: + htmlText.append(aLine.replace("\t", eightSpace)) + + self.fullHTML = htmlText return def getStyleSheet(self): diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index cc6aeb58..a1ffe11f 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -70,6 +70,8 @@ class Tokenizer(): A_PBB_AUT = 0x0020 # Page break before auto A_PBA = 0x0040 # Page break after always A_PBA_AUT = 0x0080 # Page break after auto + A_Z_TOPMRG = 0x0100 # Zero top margin + A_Z_BTMMRG = 0x0200 # Zero bottom margin def __init__(self, theProject, theParent): @@ -77,12 +79,14 @@ class Tokenizer(): self.theParent = theParent # Data Variables - self.theText = None # The raw text to be tokenized + self.theText = "" # The raw text to be tokenized self.theHandle = None # The handle associated with the text self.theItem = None # The NWItem associated with the handle - self.theTokens = None # The list of the processed tokens - self.theResult = None # The result text after conversion - self.theMarkdown = None # The result text in novelWriter markdown + self.theTokens = [] # The list of the processed tokens + 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 # User Settings self.textFont = "Serif" # Output text font @@ -96,12 +100,13 @@ class Tokenizer(): self.doKeywords = False # Also process keywords like tags and references ## Title Margins - self.marginTitle = (1.00, 0.50) - self.marginHead1 = (1.00, 0.50) - self.marginHead2 = (0.85, 0.50) - self.marginHead3 = (0.58, 0.50) - self.marginHead4 = (0.58, 0.50) - self.marginText = (0.00, 0.58) + self.marginTitle = (1.000, 0.500) + self.marginHead1 = (1.000, 0.500) + self.marginHead2 = (0.834, 0.500) + self.marginHead3 = (0.584, 0.500) + self.marginHead4 = (0.584, 0.500) + self.marginText = (0.000, 0.584) + self.marginMeta = (0.000, 0.584) ## Title Formats self.fmtTitle = "%title%" # Formatting for titles @@ -202,6 +207,10 @@ class Tokenizer(): self.marginText = (float(mUpper), float(mLower)) return + def setMetaMargins(self, mUpper, mLower): + self.marginMeta = (float(mUpper), float(mLower)) + return + def setLinkHeaders(self, linkHeaders): self.linkHeaders = linkHeaders return @@ -222,6 +231,10 @@ class Tokenizer(): self.doKeywords = doKeywords return + def setKeepMarkdown(self, keepMarkdown): + self.keepMarkdown = keepMarkdown + return + ## # Class Methods ## @@ -241,7 +254,8 @@ class Tokenizer(): self.theTokens.append(( self.T_TITLE, 0, theTitle, None, self.A_PBB | self.A_CENTRE )) - self.theMarkdown = "# %s\n\n" % theTitle + if self.keepMarkdown: + self.theMarkdown.append("# %s\n\n" % theTitle) return True @@ -283,23 +297,6 @@ class Tokenizer(): return True - def getResult(self): - """Return the result from the conversion. - """ - return self.theResult - - def getResultSize(self): - """Return the size of the result from the conversion. - """ - if self.theResult is None: - return 0 - return len(self.theResult) - - def getFilteredMarkdown(self): - """Return the novelWriter markdown after the filters have been applied. - """ - return self.theMarkdown - def doAutoReplace(self): """Run through the user's auto-replace dictionary. """ @@ -352,7 +349,6 @@ class Tokenizer(): ] self.theTokens = [] - self.theMarkdown = "" tmpMarkdown = [] nLine = 0 for aLine in self.theText.splitlines(): @@ -361,88 +357,61 @@ class Tokenizer(): # Tag lines starting with specific characters if len(aLine.strip()) == 0: self.theTokens.append(( - self.T_EMPTY, - nLine, - "", - None, - self.A_NONE + self.T_EMPTY, nLine, "", None, self.A_NONE )) - tmpMarkdown.append("\n") + if self.keepMarkdown: + tmpMarkdown.append("\n") elif aLine[0] == "%": cLine = aLine[1:].lstrip() synTag = cLine[:9].lower() if synTag == "synopsis:": self.theTokens.append(( - self.T_SYNOPSIS, - nLine, - cLine[9:].strip(), - None, - self.A_NONE + self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, self.A_NONE )) - if self.doSynopsis: + if self.doSynopsis and self.keepMarkdown: tmpMarkdown.append("%s\n" % aLine) else: self.theTokens.append(( - self.T_COMMENT, - nLine, - aLine[1:].strip(), - None, - self.A_NONE + self.T_COMMENT, nLine, aLine[1:].strip(), None, self.A_NONE )) - if self.doComments: + if self.doComments and self.keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[0] == "@": self.theTokens.append(( - self.T_KEYWORD, - nLine, - aLine[1:].strip(), - None, - self.A_NONE + self.T_KEYWORD, nLine, aLine[1:].strip(), None, self.A_NONE )) - if self.doKeywords: + if self.doKeywords and self.keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:2] == "# ": self.theTokens.append(( - self.T_HEAD1, - nLine, - aLine[2:].strip(), - None, - self.A_NONE + self.T_HEAD1, nLine, aLine[2:].strip(), None, self.A_NONE )) - tmpMarkdown.append("%s\n" % aLine) + if self.keepMarkdown: + tmpMarkdown.append("%s\n" % aLine) elif aLine[:3] == "## ": self.theTokens.append(( - self.T_HEAD2, - nLine, - aLine[3:].strip(), - None, - self.A_NONE + self.T_HEAD2, nLine, aLine[3:].strip(), None, self.A_NONE )) - tmpMarkdown.append("%s\n" % aLine) + if self.keepMarkdown: + tmpMarkdown.append("%s\n" % aLine) elif aLine[:4] == "### ": self.theTokens.append(( - self.T_HEAD3, - nLine, - aLine[4:].strip(), - None, - self.A_NONE + self.T_HEAD3, nLine, aLine[4:].strip(), None, self.A_NONE )) - tmpMarkdown.append("%s\n" % aLine) + if self.keepMarkdown: + tmpMarkdown.append("%s\n" % aLine) elif aLine[:5] == "#### ": self.theTokens.append(( - self.T_HEAD4, - nLine, - aLine[5:].strip(), - None, - self.A_NONE + self.T_HEAD4, nLine, aLine[5:].strip(), None, self.A_NONE )) - tmpMarkdown.append("%s\n" % aLine) + if self.keepMarkdown: + tmpMarkdown.append("%s\n" % aLine) else: if not self.doBodyText: @@ -465,26 +434,44 @@ class Tokenizer(): # sorted by position fmtPos = sorted(fmtPos, key=itemgetter(0)) self.theTokens.append(( - self.T_TEXT, - nLine, - aLine, - fmtPos, - self.A_NONE + self.T_TEXT, nLine, aLine, fmtPos, self.A_NONE )) - tmpMarkdown.append("%s\n" % aLine) + if self.keepMarkdown: + tmpMarkdown.append("%s\n" % aLine) # Always add an empty line at the end self.theTokens.append(( - self.T_EMPTY, - nLine, - "", - None, - self.A_NONE + self.T_EMPTY, nLine, "", None, self.A_NONE )) - tmpMarkdown.append("\n") + if self.keepMarkdown: + tmpMarkdown.append("\n") - self.theMarkdown = "".join(tmpMarkdown) - tmpMarkdown = [] + if self.keepMarkdown: + self.theMarkdown.append("".join(tmpMarkdown)) + + # Second Pass + # =========== + # Some items need a second pass + + pToken = (self.T_EMPTY, 0, "", None, self.A_NONE) + nToken = (self.T_EMPTY, 0, "", None, self.A_NONE) + tCount = len(self.theTokens) + for n, tToken in enumerate(self.theTokens): + + if n > 0: + pToken = self.theTokens[n-1] + if n < tCount - 1: + nToken = self.theTokens[n+1] + + if tToken[0] == self.T_KEYWORD: + aStyle = tToken[4] + if pToken[0] == self.T_KEYWORD: + aStyle |= self.A_Z_TOPMRG + if nToken[0] == self.T_KEYWORD: + aStyle |= self.A_Z_BTMMRG + self.theTokens[n] = ( + tToken[0], tToken[1], tToken[2], tToken[3], aStyle + ) return @@ -499,9 +486,7 @@ class Tokenizer(): # For novel files, we need to handle chapter numbering, scene # numbering, and scene breaks if self.isNovel: - for n in range(len(self.theTokens)): - - tToken = self.theTokens[n] + for n, tToken in enumerate(self.theTokens): # In case we see text before a scene, we reset the flag if tToken[0] == self.T_TEXT: @@ -513,11 +498,7 @@ class Tokenizer(): tTemp = self._formatHeading(self.fmtTitle, tToken[2]) self.theTokens[n] = ( - tToken[0], - tToken[1], - tTemp, - None, - self.A_NONE + tToken[0], tToken[1], tTemp, None, self.A_NONE ) elif tToken[0] == self.T_HEAD2: @@ -535,11 +516,7 @@ class Tokenizer(): # Format the chapter header self.theTokens[n] = ( - tToken[0], - tToken[1], - tTemp, - None, - self.A_PBB + tToken[0], tToken[1], tTemp, None, self.A_PBB ) # Set scene variables @@ -556,53 +533,29 @@ class Tokenizer(): tTemp = self._formatHeading(self.fmtScene, tToken[2]) if tTemp == "" and self.hideScene: self.theTokens[n] = ( - self.T_EMPTY, - tToken[1], - "", - None, - self.A_NONE + self.T_EMPTY, tToken[1], "", None, self.A_NONE ) elif tTemp == "" and not self.hideScene: if self.firstScene: self.theTokens[n] = ( - self.T_EMPTY, - tToken[1], - "", - None, - self.A_NONE + self.T_EMPTY, tToken[1], "", None, self.A_NONE ) else: self.theTokens[n] = ( - self.T_SKIP, - tToken[1], - "", - None, - self.A_NONE + self.T_SKIP, tToken[1], "", None, self.A_NONE ) elif tTemp == self.fmtScene: if self.firstScene: self.theTokens[n] = ( - self.T_EMPTY, - tToken[1], - "", - None, - self.A_NONE + self.T_EMPTY, tToken[1], "", None, self.A_NONE ) else: self.theTokens[n] = ( - self.T_SEP, - tToken[1], - tTemp, - None, - self.A_CENTRE + self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE ) else: self.theTokens[n] = ( - tToken[0], - tToken[1], - tTemp, - None, - self.A_NONE + tToken[0], tToken[1], tTemp, None, self.A_NONE ) # Definitely no longer the first scene @@ -615,35 +568,19 @@ class Tokenizer(): tTemp = self._formatHeading(self.fmtSection, tToken[2]) if tTemp == "" and self.hideSection: self.theTokens[n] = ( - self.T_EMPTY, - tToken[1], - "", - None, - self.A_NONE + self.T_EMPTY, tToken[1], "", None, self.A_NONE ) elif tTemp == "" and not self.hideSection: self.theTokens[n] = ( - self.T_SKIP, - tToken[1], - "", - None, - self.A_NONE + self.T_SKIP, tToken[1], "", None, self.A_NONE ) elif tTemp == self.fmtSection: self.theTokens[n] = ( - self.T_SEP, - tToken[1], - tTemp, - None, - self.A_CENTRE + self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE ) else: self.theTokens[n] = ( - tToken[0], - tToken[1], - tTemp, - None, - self.A_NONE + tToken[0], tToken[1], tTemp, None, self.A_NONE ) # For title page and partitions, we need to centre all text. @@ -654,28 +591,18 @@ class Tokenizer(): for n, tToken in enumerate(self.theTokens): if tToken[0] == self.T_HEAD1: if self.isTitle: + aStyle = self.A_PBB_AUT | self.A_CENTRE self.theTokens[n] = ( - self.T_TITLE, - tToken[1], - tToken[2], - tToken[3], - self.A_PBB_AUT | self.A_CENTRE + self.T_TITLE, tToken[1], tToken[2], tToken[3], aStyle ) else: + aStyle = self.A_PBB | self.A_CENTRE self.theTokens[n] = ( - tToken[0], - tToken[1], - tToken[2], - tToken[3], - self.A_PBB | self.A_CENTRE + tToken[0], tToken[1], tToken[2], tToken[3], aStyle ) else: self.theTokens[n] = ( - tToken[0], - tToken[1], - tToken[2], - tToken[3], - self.A_CENTRE + tToken[0], tToken[1], tToken[2], tToken[3], self.A_CENTRE ) # Add a page break after the last entry @@ -683,11 +610,7 @@ class Tokenizer(): if n >= 0: tToken = self.theTokens[n] self.theTokens[n] = ( - tToken[0], - tToken[1], - tToken[2], - tToken[3], - tToken[4] | self.A_PBA + tToken[0], tToken[1], tToken[2], tToken[3], tToken[4] | self.A_PBA ) # A single page is always left-aligned and starts on a fresh @@ -696,19 +619,11 @@ class Tokenizer(): for n, tToken in enumerate(self.theTokens): if n == 0: self.theTokens[n] = ( - tToken[0], - tToken[1], - tToken[2], - tToken[3], - self.A_LEFT | self.A_PBB + tToken[0], tToken[1], tToken[2], tToken[3], self.A_LEFT | self.A_PBB ) else: self.theTokens[n] = ( - tToken[0], - tToken[1], - tToken[2], - tToken[3], - self.A_LEFT + tToken[0], tToken[1], tToken[2], tToken[3], self.A_LEFT ) return True diff --git a/nw/core/toodt.py b/nw/core/toodt.py index 4eb8d57b..936c7faf 100644 --- a/nw/core/toodt.py +++ b/nw/core/toodt.py @@ -116,6 +116,7 @@ class ToOdt(Tokenizer): self._mTopHead4 = "0.247cm" self._mTopHead = "0.423cm" self._mTopText = "0.000cm" + self._mTopMeta = "0.000cm" self._mBotTitle = "0.212cm" self._mBotHead1 = "0.212cm" @@ -124,6 +125,7 @@ class ToOdt(Tokenizer): self._mBotHead4 = "0.212cm" self._mBotHead = "0.212cm" self._mBotText = "0.247cm" + self._mBotMeta = "0.106cm" ## Colour self._colHead12 = None @@ -170,7 +172,7 @@ class ToOdt(Tokenizer): self._fontFamily = self.textFont if len(self.textFont.split()) > 1: - self._fontFamily = f"'{self.textFont}'" + self._fontFamily = f"'{self.textFont}'" self._fontPitch = "fixed" if self.textFixed else "variable" self._fSizeTitle = f"{round(2.50 * self.textSize):d}pt" @@ -188,6 +190,7 @@ class ToOdt(Tokenizer): 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]) self._mBotTitle = self._emToCm(self.marginTitle[1]) self._mBotHead1 = self._emToCm(self.marginHead1[1]) @@ -196,13 +199,14 @@ class ToOdt(Tokenizer): 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]) if self.colourHead: self._colHead12 = "#2a6099" self._opaHead12 = "100%" self._colHead34 = "#444444" self._opaHead34 = "100%" - self._colMetaTx = "#666666" + self._colMetaTx = "#813709" self._opaMetaTx = "100%" self._lineHeight = f"{round(100 * self.lineHeight):d}%" @@ -302,21 +306,28 @@ class ToOdt(Tokenizer): if tStyle is not None: if tStyle & self.A_LEFT: oStyle.setTextAlign("left") - if tStyle & self.A_RIGHT: + elif tStyle & self.A_RIGHT: oStyle.setTextAlign("right") - if tStyle & self.A_CENTRE: + elif tStyle & self.A_CENTRE: oStyle.setTextAlign("center") - if tStyle & self.A_JUSTIFY: + elif tStyle & self.A_JUSTIFY: oStyle.setTextAlign("justify") + if tStyle & self.A_PBB: oStyle.setBreakBefore("page") - if tStyle & self.A_PBB_AUT: + elif tStyle & self.A_PBB_AUT: oStyle.setBreakBefore("auto") + if tStyle & self.A_PBA: oStyle.setBreakAfter("page") - if tStyle & self.A_PBA_AUT: + elif tStyle & self.A_PBA_AUT: oStyle.setBreakAfter("auto") + if tStyle & self.A_Z_BTMMRG: + oStyle.setMarginBottom("0.000cm") + if tStyle & self.A_Z_TOPMRG: + oStyle.setMarginTop("0.000cm") + # Process Text Types if tType == self.T_EMPTY: if hasHardBreak and parStyle is not None: @@ -740,8 +751,8 @@ class ToOdt(Tokenizer): oStyle.setDisplayName("Text Meta") oStyle.setParentStyleName("Standard") oStyle.setClass("text") - oStyle.setMarginTop(self._mTopText) - oStyle.setMarginBottom(self._mBotText) + oStyle.setMarginTop(self._mTopMeta) + oStyle.setMarginBottom(self._mBotMeta) oStyle.setLineHeight(self._lineHeight) oStyle.setFontName(self.textFont) oStyle.setFontFamily(self._fontFamily) diff --git a/nw/gui/build.py b/nw/gui/build.py index 56190cec..d05aa0cc 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -79,7 +79,6 @@ class GuiBuildNovel(QDialog): self.htmlText = [] # List of html documents self.htmlStyle = [] # List of html styles - self.nwdText = [] # List of markdown documents self.htmlSize = 0 # Size of the html document self.buildTime = 0 # The timestamp of the last build @@ -532,7 +531,6 @@ class GuiBuildNovel(QDialog): else: self.htmlText = [] self.htmlStyle = [] - self.nwdText = [] self.buildTime = 0 return False @@ -552,34 +550,26 @@ class GuiBuildNovel(QDialog): textSize = self.textSize.value() replaceTabs = self.replaceTabs.isChecked() - tStart = int(time()) - self.htmlText = [] self.htmlStyle = [] - self.nwdText = [] self.htmlSize = 0 + # Build Preview + # ============= + makeHtml = ToHtml(self.theProject, self.theParent) - self._doBuild(makeHtml) - + self._doBuild(makeHtml, isPreview=True) if replaceTabs: - htmlText = [] - eightSpace = " "*8 - for aLine in self.htmlText: - htmlText.append(aLine.replace("\t", eightSpace)) - self.htmlText = htmlText + makeHtml.replaceTabs() - nwdText = [] - for aLine in self.nwdText: - nwdText.append(aLine.replace("\t", " ")) - self.nwdText = nwdText - - tEnd = int(time()) - logger.debug("Built project in %.3f ms" % (1000*(tEnd - tStart))) + self.htmlText = makeHtml.fullHTML self.htmlStyle = makeHtml.getStyleSheet() - self.buildTime = tEnd + self.htmlSize = makeHtml.getFullResultSize() + self.buildTime = int(time()) + + # Load Preview + # ============ - # Load the preview document with the html data self.docView.setTextFont(textFont, textSize) self.docView.setJustify(justifyText) if noStyling: @@ -600,9 +590,11 @@ class GuiBuildNovel(QDialog): return - def _doBuild(self, bldObj): + def _doBuild(self, bldObj, isPreview=False, doConvert=True): """Rund the build with a specific build object. """ + tStart = int(time()) + # Get Settings fmtTitle = self.fmtTitle.text().strip() fmtChapter = self.fmtChapter.text().strip() @@ -644,7 +636,6 @@ class GuiBuildNovel(QDialog): if isHtml: bldObj.setStyles(not noStyling) - self.htmlSize = 0 if isOdt: bldObj.setColourHeaders(not noStyling) @@ -667,31 +658,27 @@ class GuiBuildNovel(QDialog): if noteRoot: # Add headers for root folders of notes bldObj.addRootHeading(tItem.itemHandle) - bldObj.doConvert() - if isHtml: - self.htmlText.append(bldObj.getResult()) - self.nwdText.append(bldObj.getFilteredMarkdown()) - self.htmlSize += bldObj.getResultSize() + if doConvert: + bldObj.doConvert() elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag): bldObj.setText(tItem.itemHandle) bldObj.doAutoReplace() bldObj.tokenizeText() bldObj.doHeaders() - bldObj.doConvert() + if doConvert: + bldObj.doConvert() bldObj.doPostProcessing() - if isHtml: - self.htmlText.append(bldObj.getResult()) - self.nwdText.append(bldObj.getFilteredMarkdown()) - self.htmlSize += bldObj.getResultSize() except Exception as e: logger.error("Failed to generate html of document '%s'" % tItem.itemHandle) logger.error(str(e)) - self.docView.setText(( - "Failed to generate preview. " - "Document with title '%s' could not be parsed." - ) % tItem.itemName) + if isPreview: + self.docView.setText(( + "Failed to generate preview. " + "Document with title '%s' could not be parsed." + ) % tItem.itemName) + return False # Update progress bar, also for skipped items @@ -700,6 +687,9 @@ class GuiBuildNovel(QDialog): if isOdt: bldObj.closeDocument() + tEnd = int(time()) + logger.debug("Built project in %.3f ms" % (1000*(tEnd - tStart))) + if bldObj.errData: self.theParent.makeAlert(( "There were problems when building the project:" @@ -751,63 +741,59 @@ class GuiBuildNovel(QDialog): def _saveDocument(self, theFormat): """Save the document to various formats. """ + replaceTabs = self.replaceTabs.isChecked() + byteFmt = QByteArray() fileExt = "" textFmt = "" - outTool = "" - # Create the settings + # Settings + # ======== + if theFormat == self.FMT_ODT: fileExt = "odt" textFmt = "Open Document" - outTool = "NW_ODT" elif theFormat == self.FMT_FODT: fileExt = "fodt" textFmt = "Flat Open Document" - outTool = "NW_ODT" elif theFormat == self.FMT_PDF: fileExt = "pdf" textFmt = "PDF" - outTool = "QtPrint" elif theFormat == self.FMT_HTM: fileExt = "htm" textFmt = "Plain HTML" - outTool = "NW" elif theFormat == self.FMT_MD: byteFmt.append("markdown") fileExt = "md" textFmt = "Markdown" - outTool = "Qt" elif theFormat == self.FMT_NWD: fileExt = "nwd" textFmt = "%s Markdown" % nw.__package__ - outTool = "NW" elif theFormat == self.FMT_TXT: byteFmt.append("plaintext") fileExt = "txt" textFmt = "Plain Text" - outTool = "Qt" elif theFormat == self.FMT_JSON_H: fileExt = "json" textFmt = "JSON + %s HTML" % nw.__package__ - outTool = "NW" elif theFormat == self.FMT_JSON_M: fileExt = "json" textFmt = "JSON + %s Markdown" % nw.__package__ - outTool = "NW" else: return False - # Generate the file name + # Generate File Name + # ================== + if fileExt: cleanName = makeFileNameSafe(self.theProject.projName) @@ -830,109 +816,109 @@ class GuiBuildNovel(QDialog): else: return False - # Do the actual writing - wSuccess = False + # Build and Write + # =============== + errMsg = "" - if outTool == "Qt": + wSuccess = False + + if theFormat == self.FMT_MD or theFormat == self.FMT_TXT: docWriter = QTextDocumentWriter() docWriter.setFileName(savePath) docWriter.setFormat(byteFmt) wSuccess = docWriter.write(self.docView.qDocument) - elif outTool == "NW": + elif theFormat == self.FMT_HTM: + makeHtml = ToHtml(self.theProject, self.theParent) + self._doBuild(makeHtml) + if replaceTabs: + makeHtml.replaceTabs() + try: - with open(savePath, mode="w", encoding="utf8") as outFile: - if theFormat == self.FMT_HTM: - # Write novelWriter HTML data - theStyle = self.htmlStyle.copy() - theStyle.append(r"article {width: 800px; margin: 40px auto;}") - bodyText = "".join(self.htmlText) - bodyText = bodyText.replace("\t", " ") - - theHtml = ( - "\n" - "\n" - "\n" - "\n" - "{projTitle:s}\n" - "\n" - "\n" - "\n" - "
\n" - "{bodyText:s}\n" - "
\n" - "\n" - "\n" - ).format( - projTitle = self.theProject.projName, - htmlStyle = "\n".join(theStyle), - bodyText = bodyText, - ) - outFile.write(theHtml) - - elif theFormat == self.FMT_NWD: - # Write novelWriter markdown data - for aLine in self.nwdText: - outFile.write(aLine) - - elif theFormat == self.FMT_JSON_H or theFormat == self.FMT_JSON_M: - jsonData = { - "meta" : { - "workingTitle" : self.theProject.projName, - "novelTitle" : self.theProject.bookTitle, - "authors" : self.theProject.bookAuthors, - "buildTime" : self.buildTime, - } - } - - if theFormat == self.FMT_JSON_H: - theBody = [] - for htmlPage in self.htmlText: - theBody.append(htmlPage.rstrip("\n").split("\n")) - jsonData["text"] = { - "css" : self.htmlStyle, - "html" : theBody, - } - elif theFormat == self.FMT_JSON_M: - theBody = [] - for nwdPage in self.nwdText: - theBody.append(nwdPage.split("\n")) - jsonData["text"] = { - "nwd" : theBody, - } - - outFile.write(json.dumps(jsonData, indent=2)) - + makeHtml.saveHTML5(savePath) wSuccess = True - except Exception as e: errMsg = str(e) - elif outTool == "NW_ODT": + elif theFormat == self.FMT_NWD: + makeNwd = ToHtml(self.theProject, self.theParent) + makeNwd.setKeepMarkdown(True) + self._doBuild(makeNwd, doConvert=False) + if replaceTabs: + makeNwd.replaceTabs(spaceChar=" ") - if theFormat == self.FMT_FODT: - makeOdt = ToOdt(self.theProject, self.theParent, isFlat=True) - self._doBuild(makeOdt) - try: - makeOdt.saveFlatXML(savePath) + try: + with open(savePath, mode="w", encoding="utf8") as outFile: + for nwdPage in makeNwd.theMarkdown: + outFile.write(nwdPage) + wSuccess = True + except Exception as e: + errMsg = str(e) + + elif theFormat == self.FMT_FODT: + makeOdt = ToOdt(self.theProject, self.theParent, isFlat=True) + self._doBuild(makeOdt) + try: + makeOdt.saveFlatXML(savePath) + wSuccess = True + except Exception as e: + errMsg = str(e) + + elif theFormat == self.FMT_ODT: + makeOdt = ToOdt(self.theProject, self.theParent, isFlat=False) + self._doBuild(makeOdt) + try: + makeOdt.saveOpenDocText(savePath) + wSuccess = True + except Exception as e: + errMsg = str(e) + + elif theFormat == self.FMT_JSON_H or theFormat == self.FMT_JSON_M: + jsonData = { + "meta" : { + "workingTitle" : self.theProject.projName, + "novelTitle" : self.theProject.bookTitle, + "authors" : self.theProject.bookAuthors, + "buildTime" : self.buildTime, + } + } + + if theFormat == self.FMT_JSON_H: + makeHtml = ToHtml(self.theProject, self.theParent) + self._doBuild(makeHtml) + if replaceTabs: + makeHtml.replaceTabs() + + theBody = [] + for htmlPage in makeHtml.fullHTML: + theBody.append(htmlPage.rstrip("\n").split("\n")) + jsonData["text"] = { + "css" : self.htmlStyle, + "html" : theBody, + } + + elif theFormat == self.FMT_JSON_M: + makeNwd = ToHtml(self.theProject, self.theParent) + makeNwd.setKeepMarkdown(True) + self._doBuild(makeNwd, doConvert=False) + if replaceTabs: + makeNwd.replaceTabs(spaceChar=" ") + + theBody = [] + for nwdPage in makeNwd.theMarkdown: + theBody.append(nwdPage.split("\n")) + jsonData["text"] = { + "nwd" : theBody, + } + + try: + with open(savePath, mode="w", encoding="utf8") as outFile: + outFile.write(json.dumps(jsonData, indent=2)) wSuccess = True + except Exception as e: + errMsg = str(e) - except Exception as e: - errMsg = str(e) - - elif theFormat == self.FMT_ODT: - makeOdt = ToOdt(self.theProject, self.theParent, isFlat=False) - self._doBuild(makeOdt) - try: - makeOdt.saveOpenDocText(savePath) - wSuccess = True - - except Exception as e: - errMsg = str(e) - - elif outTool == "QtPrint" and theFormat == self.FMT_PDF: + elif theFormat == self.FMT_PDF: try: thePrinter = QPrinter() thePrinter.setOutputFormat(QPrinter.PdfFormat) @@ -1021,13 +1007,10 @@ class GuiBuildNovel(QDialog): if "htmlStyle" in theData.keys(): self.htmlStyle = theData["htmlStyle"] dataCount += 1 - if "nwdText" in theData.keys(): - self.nwdText = theData["nwdText"] - dataCount += 1 if "buildTime" in theData.keys(): self.buildTime = theData["buildTime"] - return dataCount == 3 + return dataCount == 2 def _saveCache(self): """Save the current data to cache. @@ -1040,7 +1023,6 @@ class GuiBuildNovel(QDialog): outFile.write(json.dumps({ "htmlText" : self.htmlText, "htmlStyle" : self.htmlStyle, - "nwdText" : self.nwdText, "buildTime" : self.buildTime, }, indent=2)) except Exception as e: