diff --git a/nw/config.py b/nw/config.py
index 695b31e0..e07852c6 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -19,7 +19,7 @@ from os import path, mkdir, makedirs, getcwd
from appdirs import user_config_dir
from datetime import datetime
-from nw.constants import nwFiles
+from nw.constants import nwFiles, nwUnicode
from nw.common import splitVersionNumber
from PyQt5.Qt import PYQT_VERSION_STR
@@ -87,8 +87,8 @@ class Config:
self.doReplaceDots = True
self.wordCountTimer = 5.0
- self.fmtSingleQuotes = ["\u2018","\u2019"]
- self.fmtDoubleQuotes = ["\u201c","\u201d"]
+ self.fmtSingleQuotes = [nwUnicode.U_LSQUO,nwUnicode.U_RSQUO]
+ self.fmtDoubleQuotes = [nwUnicode.U_LDQUO,nwUnicode.U_RDQUO]
self.spellLanguage = "en_GB"
diff --git a/nw/constants.py b/nw/constants.py
index 4fa6ab02..92c693dc 100644
--- a/nw/constants.py
+++ b/nw/constants.py
@@ -142,26 +142,74 @@ class nwQuotes():
"\u300e", # Left white corner bracket
"\u300f", # Right white corner bracket
]
- HTML = {
- "\u0022" : """,
- "\u0027" : "'",
- "\u00ab" : "«",
- "\u00bb" : "»",
- "\u2018" : "‘",
- "\u2019" : "’",
- "\u201a" : "‚",
- "\u201b" : "‛",
- "\u201c" : "“",
- "\u201d" : "”",
- "\u201e" : "„",
- "\u201f" : "‟",
- "\u2039" : "‹",
- "\u203a" : "›",
- "\u2e42" : "⹂",
- "\u300c" : "「",
- "\u300d" : "」",
- "\u300e" : "『",
- "\u300f" : "』",
- }
-# END Class nwQuotes
\ No newline at end of file
+# END Class nwQuotes
+
+class nwUnicode:
+ """Suppoted unicode character constants and translation maps.
+ """
+
+ # Unicode Constants
+
+ ## Quotation Marks
+ U_QUOT = "\u0022" # Quotation mark
+ U_APOS = "\u0027" # Apostrophe
+ U_LAQUO = "\u00ab" # Left-pointing double angle quotation mark
+ U_RAQUO = "\u00bb" # Right-pointing double angle quotation mark
+ U_LSQUO = "\u2018" # Left single quotation mark
+ U_RSQUO = "\u2019" # Right single quotation mark
+ U_SBQUO = "\u201a" # Single low-9 quotation mark
+ U_SUQUO = "\u201b" # Single high-reversed-9 quotation mark
+ U_LDQUO = "\u201c" # Left double quotation mark
+ U_RDQUO = "\u201d" # Right double quotation mark
+ U_BDQUO = "\u201e" # Double low-9 quotation mark
+ U_UDQUO = "\u201f" # Double high-reversed-9 quotation mark
+ U_LSAQUO = "\u2039" # Single left-pointing angle quotation mark
+ U_RSAQUO = "\u203a" # Single right-pointing angle quotation mark
+ U_BDRQUO = "\u2e42" # Double low-reversed-9 quotation mark
+ 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
+
+ ## Punctuation
+ U_ENDASH = "\u2013" # Short dash
+ U_EMDASH = "\u2014" # Long dash
+ U_HELLIP = "\u2026" # Ellipsis
+
+ ## Other
+ U_NBSP = "\u00a0" # Non-breaking space
+ U_PARA = "\u2029" # Paragraph separator
+
+ # HTML Equivalents
+
+ ## Quotes
+ H_QUOT = """
+ H_APOS = "'"
+ H_LAQUO = "«"
+ H_RAQUO = "»"
+ H_LSQUO = "‘"
+ H_RSQUO = "’"
+ H_SBQUO = "‚"
+ H_SUQUO = "‛"
+ H_LDQUO = "“"
+ H_RDQUO = "”"
+ H_BDQUO = "„"
+ H_UDQUO = "‟"
+ H_LSAQUO = "‹"
+ H_RSAQUO = "›"
+ H_BDRQUO = "⹂"
+ H_LCQUO = "「"
+ H_RCQUO = "」"
+ H_LWCQUO = "『"
+ H_LWCQUO = "『"
+
+ ## Punctuation
+ H_ENDASH = "–"
+ H_EMDASH = "—"
+ H_HELLIP = "…"
+
+ ## Other
+ H_NBSP = " "
+
+# END Class nwUnicode
diff --git a/nw/convert/file/html.py b/nw/convert/file/html.py
index 278acab7..1dc89b39 100644
--- a/nw/convert/file/html.py
+++ b/nw/convert/file/html.py
@@ -38,18 +38,19 @@ class HtmlFile(TextFile):
self.outFile.write("\n")
self.outFile.write("\n")
self.outFile.write("
\n")
- self.outFile.write("\n")
+ self.outFile.write(" \n")
+ self.outFile.write(" \n")
self.outFile.write("\n")
self.outFile.write("\n")
self.outFile.write("\n")
diff --git a/nw/convert/file/text.py b/nw/convert/file/text.py
index 9c490b3d..9301a823 100644
--- a/nw/convert/file/text.py
+++ b/nw/convert/file/text.py
@@ -138,6 +138,7 @@ class TextFile():
self.theConv.tokenizeText()
self.theConv.doHeaders()
self.theConv.doConvert()
+ self.theConv.doPostProcessing()
if self.theConv.theResult is not None and self.outFile is not None:
self.outFile.write(self.theConv.theResult)
diff --git a/nw/convert/text/tohtml.py b/nw/convert/text/tohtml.py
index 0c086a68..64ddbbb7 100644
--- a/nw/convert/text/tohtml.py
+++ b/nw/convert/text/tohtml.py
@@ -15,6 +15,7 @@ import re
import nw
from nw.convert.tokenizer import Tokenizer
+from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -28,13 +29,13 @@ class ToHtml(Tokenizer):
Tokenizer.doAutoReplace(self)
repDict = {
- "<" : "<",
- ">" : ">",
- "&" : "&",
- "\u2013" : "&endash;",
- "\u2014" : "$emdash;",
- "\u2500" : "$emdash;",
- "\u2026" : "…",
+ "<" : "<",
+ ">" : ">",
+ "&" : "&",
+ nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
+ nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
+ nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
+ nwUnicode.U_NBSP : nwUnicode.H_NBSP,
}
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)
diff --git a/nw/convert/text/tolatex.py b/nw/convert/text/tolatex.py
index cfcec012..435fee84 100644
--- a/nw/convert/text/tolatex.py
+++ b/nw/convert/text/tolatex.py
@@ -16,6 +16,7 @@ import re
import nw
from nw.convert.tokenizer import Tokenizer
+from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -26,6 +27,20 @@ class ToLaTeX(Tokenizer):
self.texCodecFail = False
return
+ def doPostProcessing(self):
+ """The latexcodec misses dashes and non-breaking spaces, so we do those here.
+ """
+
+ repDict = {
+ nwUnicode.U_ENDASH : "--",
+ nwUnicode.U_EMDASH : "---",
+ nwUnicode.U_NBSP : "~",
+ }
+ xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
+ self.theResult = xRep.sub(lambda x: repDict[x.group(0)], self.theResult)
+
+ return
+
def doConvert(self):
texTags = {
diff --git a/nw/convert/text/totext.py b/nw/convert/text/totext.py
index fc21163e..5b226999 100644
--- a/nw/convert/text/totext.py
+++ b/nw/convert/text/totext.py
@@ -16,6 +16,7 @@ import re
import nw
from nw.convert.tokenizer import Tokenizer
+from nw.constants import nwUnicode
logger = logging.getLogger(__name__)
@@ -25,6 +26,17 @@ class ToText(Tokenizer):
Tokenizer.__init__(self, theProject, theParent)
return
+ def doAutoReplace(self):
+ Tokenizer.doAutoReplace(self)
+
+ repDict = {
+ nwUnicode.U_NBSP : " ",
+ }
+ 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)
+
+ return
+
def doConvert(self):
"""Converts the tokenized text into plain text.
"""
diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py
index 63abc1ae..7be25952 100644
--- a/nw/convert/tokenizer.py
+++ b/nw/convert/tokenizer.py
@@ -149,6 +149,9 @@ class Tokenizer():
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
return
+ def doPostProcessing(self):
+ return
+
def tokenizeText(self):
"""Scan the text for either lines starting with specific characters that indicate headers,
comments, commands etc, or just contains plain text. in the case of plain text, apply the
diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py
index 03d3313d..1d0c35ac 100644
--- a/nw/gui/doceditor.py
+++ b/nw/gui/doceditor.py
@@ -23,7 +23,7 @@ from nw.project.document import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter
from nw.gui.wordcounter import WordCounter
from nw.tools.spellcheck import NWSpellCheck
-from nw.constants import nwFiles
+from nw.constants import nwFiles, nwUnicode
from nw.enum import nwDocAction, nwAlert
logger = logging.getLogger(__name__)
@@ -218,7 +218,14 @@ class GuiDocEditor(QTextEdit):
return self.docChanged
def getText(self):
- theText = self.toPlainText()
+ """Get the text content of the current document. This method uses QTextEdit->toPlainText for
+ Qt versions lower than 5.9, and the QDocument->toRawText for higher version. The latter
+ preserves non-breaking spaces, which the former does not.
+ """
+ if self.mainConf.verQtValue >= 50900:
+ theText = self.qDocument.toRawText().replace(nwUnicode.U_PARA,"\n")
+ else:
+ theText = self.toPlainText()
return theText
def setCursorPosition(self, thePosition):
@@ -316,9 +323,26 @@ class GuiDocEditor(QTextEdit):
to know whether we had a selection prior to triggering the _docChange slot, as we do not
want to trigger autoreplace on selections. Autoreplace on selections messes with undo/redo
history.
+ We also need to intercept the Shift key modifier for certain key combinations that modifies
+ standard keys like enter and space. However, we don't want to spend a lot of time in this
+ function as it is triggered on every keypress when typing.
"""
+
self.hasSelection = self.textCursor().hasSelection()
- QTextEdit.keyPressEvent(self, keyEvent)
+
+ if keyEvent.modifiers() == Qt.ShiftModifier:
+ theKey = keyEvent.key()
+ if theKey == Qt.Key_Return:
+ self._insertHardBreak()
+ elif theKey == Qt.Key_Enter:
+ self._insertHardBreak()
+ elif theKey == Qt.Key_Space:
+ self._insertNonBreakingSpace()
+ else:
+ QTextEdit.keyPressEvent(self, keyEvent)
+ else:
+ QTextEdit.keyPressEvent(self, keyEvent)
+
return
##
@@ -332,6 +356,13 @@ class GuiDocEditor(QTextEdit):
theCursor.endEditBlock()
return
+ def _insertNonBreakingSpace(self):
+ theCursor = self.textCursor()
+ theCursor.beginEditBlock()
+ theCursor.insertText(nwUnicode.U_NBSP)
+ theCursor.endEditBlock()
+ return
+
def _openSpellContext(self):
self._openContextMenu(self.cursorRect().center())
return
@@ -442,15 +473,15 @@ class GuiDocEditor(QTextEdit):
elif self.mainConf.doReplaceDash and theTwo == "--":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
- theCursor.insertText("\u2013")
+ theCursor.insertText(nwUnicode.U_ENDASH)
- elif self.mainConf.doReplaceDash and theTwo == "\u2013-":
+ elif self.mainConf.doReplaceDash and theTwo == nwUnicode.U_ENDASH+"-":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
- theCursor.insertText("\u2014")
+ theCursor.insertText(nwUnicode.U_EMDASH)
elif self.mainConf.doReplaceDots and theThree == "...":
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 3)
- theCursor.insertText("\u2026")
+ theCursor.insertText(nwUnicode.U_HELLIP)
return
diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py
index 74506f71..e356dbf7 100644
--- a/nw/gui/dochighlight.py
+++ b/nw/gui/dochighlight.py
@@ -16,6 +16,8 @@ import nw
from PyQt5.QtCore import Qt, QRegularExpression
from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
+from nw.constants import nwUnicode
+
logger = logging.getLogger(__name__)
class GuiDocHighlighter(QSyntaxHighlighter):
@@ -87,6 +89,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"strike" : self._makeFormat(self.colEmph, "strike"),
"underline" : self._makeFormat(self.colEmph, "underline"),
"trailing" : self._makeFormat(self.colTrail,"background"),
+ "nobreak" : self._makeFormat(self.colTrail,"background"),
"dialogue1" : self._makeFormat(self.colDialN),
"dialogue2" : self._makeFormat(self.colDialD),
"dialogue3" : self._makeFormat(self.colDialS),
@@ -145,6 +148,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
}
))
+ # Non-breaking Space
+ self.hRules.append((
+ "[\u00a0]+", {
+ 0 : self.hStyles["nobreak"],
+ }
+ ))
+
# Markdown
self.hRules.append((
r"(?, and this C with . While is just .
-
+Let’s also add some text with a non-breaking space in it, like right here!
diff --git a/sample/sampleNovel/data_b/a8a28a246524_main.nwd b/sample/sampleNovel/data_b/a8a28a246524_main.nwd
index 24790377..fd3199ca 100644
--- a/sample/sampleNovel/data_b/a8a28a246524_main.nwd
+++ b/sample/sampleNovel/data_b/a8a28a246524_main.nwd
@@ -13,6 +13,3 @@ With many cheerful facts about the square of the hypotenuse
With many cheerful facts about the square of the hypotenuse
With many cheerful facts about the square of the hypotenuse
With many cheerful facts about the square of the hypotepotenuse
-
-
-
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx
index 1412184e..c551ad8d 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/sampleNovel/nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Sample Project
Sample Project
@@ -11,7 +11,7 @@
True
ba8a28a246524
ba8a28a246524
- 859
+ 873
B
E
@@ -79,10 +79,10 @@
Notes
False
SCENE
- 713
- 132
- 6
- 72
+ 787
+ 146
+ 7
+ 152
-
Another Scene
@@ -103,10 +103,10 @@
Finished
False
UNNUMBERED
- 626
+ 630
101
3
- 583
+ 648
-
A Note on Ipsums