novelWriter 2.7.4__py3-none-any.whl → 2.8b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. novelwriter/__init__.py +8 -7
  2. novelwriter/assets/icons/font_awesome.icons +22 -4
  3. novelwriter/assets/icons/material_filled_normal.icons +20 -2
  4. novelwriter/assets/icons/material_filled_thin.icons +20 -2
  5. novelwriter/assets/icons/material_rounded_normal.icons +20 -2
  6. novelwriter/assets/icons/material_rounded_thin.icons +20 -2
  7. novelwriter/assets/icons/material_sharp_normal.icons +20 -2
  8. novelwriter/assets/icons/material_sharp_thin.icons +20 -2
  9. novelwriter/assets/icons/remix_filled.icons +20 -2
  10. novelwriter/assets/icons/remix_outline.icons +20 -2
  11. novelwriter/assets/images/welcome.webp +0 -0
  12. novelwriter/assets/manual.pdf +0 -0
  13. novelwriter/assets/manual_fr.pdf +0 -0
  14. novelwriter/assets/sample.zip +0 -0
  15. novelwriter/assets/text/credits_en.htm +61 -11
  16. novelwriter/assets/themes/aura.conf +97 -0
  17. novelwriter/assets/themes/aura_bright.conf +95 -0
  18. novelwriter/assets/themes/aura_soft.conf +97 -0
  19. novelwriter/assets/themes/b2t_garden_dark.conf +97 -0
  20. novelwriter/assets/themes/b2t_garden_light.conf +97 -0
  21. novelwriter/assets/themes/b2t_suburb_dark.conf +97 -0
  22. novelwriter/assets/themes/b2t_suburb_light.conf +97 -0
  23. novelwriter/assets/themes/b4t_classic_o_dark.conf +97 -0
  24. novelwriter/assets/themes/b4t_classic_o_light.conf +97 -0
  25. novelwriter/assets/themes/b4t_modern_c_dark.conf +97 -0
  26. novelwriter/assets/themes/b4t_modern_c_light.conf +97 -0
  27. novelwriter/assets/themes/blue_streak_dark.conf +97 -0
  28. novelwriter/assets/themes/blue_streak_light.conf +97 -0
  29. novelwriter/assets/themes/castle_day.conf +95 -0
  30. novelwriter/assets/themes/castle_night.conf +95 -0
  31. novelwriter/assets/themes/catppuccin_latte.conf +97 -0
  32. novelwriter/assets/themes/catppuccin_mocha.conf +97 -0
  33. novelwriter/assets/themes/chalky_soil.conf +95 -0
  34. novelwriter/assets/themes/chernozem.conf +95 -0
  35. novelwriter/assets/themes/cyberpunk_night.conf +88 -40
  36. novelwriter/assets/themes/default_dark.conf +89 -41
  37. novelwriter/assets/themes/default_light.conf +89 -41
  38. novelwriter/assets/themes/dracula.conf +91 -42
  39. novelwriter/assets/themes/espresso.conf +97 -0
  40. novelwriter/assets/themes/everforest_dark.conf +97 -0
  41. novelwriter/assets/themes/everforest_light.conf +97 -0
  42. novelwriter/assets/themes/floral_daydream.conf +95 -0
  43. novelwriter/assets/themes/floral_midnight.conf +95 -0
  44. novelwriter/assets/themes/full_moon.conf +95 -0
  45. novelwriter/assets/themes/grey_dark.conf +97 -0
  46. novelwriter/assets/themes/grey_light.conf +97 -0
  47. novelwriter/assets/themes/horizon_dark.conf +97 -0
  48. novelwriter/assets/themes/horizon_light.conf +97 -0
  49. novelwriter/assets/themes/jewel_case_dark.conf +95 -0
  50. novelwriter/assets/themes/jewel_case_light.conf +95 -0
  51. novelwriter/assets/themes/lcars.conf +97 -0
  52. novelwriter/assets/themes/light_owl.conf +117 -0
  53. novelwriter/assets/themes/new_moon.conf +97 -0
  54. novelwriter/assets/themes/night_owl.conf +117 -0
  55. novelwriter/assets/themes/noctis.conf +129 -0
  56. novelwriter/assets/themes/noctis_lux.conf +129 -0
  57. novelwriter/assets/themes/nord.conf +97 -0
  58. novelwriter/assets/themes/nordlicht.conf +95 -0
  59. novelwriter/assets/themes/otium_dark.conf +95 -0
  60. novelwriter/assets/themes/otium_light.conf +95 -0
  61. novelwriter/assets/themes/paragon.conf +96 -0
  62. novelwriter/assets/themes/primer_light.conf +97 -0
  63. novelwriter/assets/themes/primer_night.conf +97 -0
  64. novelwriter/assets/themes/rose_pine.conf +97 -0
  65. novelwriter/assets/themes/rose_pine_dawn.conf +97 -0
  66. novelwriter/assets/themes/ruby_day.conf +95 -0
  67. novelwriter/assets/themes/ruby_night.conf +95 -0
  68. novelwriter/assets/themes/selenium_dark.conf +95 -0
  69. novelwriter/assets/themes/selenium_light.conf +95 -0
  70. novelwriter/assets/themes/sepia_dark.conf +95 -0
  71. novelwriter/assets/themes/sepia_light.conf +95 -0
  72. novelwriter/assets/themes/snazzy.conf +102 -40
  73. novelwriter/assets/themes/solarized_dark.conf +108 -40
  74. novelwriter/assets/themes/solarized_light.conf +108 -40
  75. novelwriter/assets/themes/sultana_light.conf +95 -0
  76. novelwriter/assets/themes/sultana_night.conf +95 -0
  77. novelwriter/assets/themes/tango_dark.conf +111 -0
  78. novelwriter/assets/themes/tango_light.conf +111 -0
  79. novelwriter/assets/themes/tomorrow.conf +117 -0
  80. novelwriter/assets/themes/tomorrow_night.conf +117 -0
  81. novelwriter/assets/themes/tomorrow_night_blue.conf +117 -0
  82. novelwriter/assets/themes/tomorrow_night_bright.conf +117 -0
  83. novelwriter/assets/themes/tomorrow_night_eighties.conf +117 -0
  84. novelwriter/assets/themes/vivid_black_green.conf +97 -0
  85. novelwriter/assets/themes/vivid_black_red.conf +97 -0
  86. novelwriter/assets/themes/vivid_white_green.conf +97 -0
  87. novelwriter/assets/themes/vivid_white_red.conf +97 -0
  88. novelwriter/assets/themes/warpgate.conf +96 -0
  89. novelwriter/assets/themes/waterlily_dark.conf +95 -0
  90. novelwriter/assets/themes/waterlily_light.conf +95 -0
  91. novelwriter/common.py +47 -17
  92. novelwriter/config.py +57 -62
  93. novelwriter/constants.py +32 -6
  94. novelwriter/core/buildsettings.py +3 -23
  95. novelwriter/core/coretools.py +21 -25
  96. novelwriter/core/docbuild.py +4 -9
  97. novelwriter/core/document.py +2 -6
  98. novelwriter/core/index.py +33 -53
  99. novelwriter/core/indexdata.py +17 -22
  100. novelwriter/core/item.py +11 -35
  101. novelwriter/core/itemmodel.py +5 -21
  102. novelwriter/core/novelmodel.py +3 -7
  103. novelwriter/core/options.py +3 -4
  104. novelwriter/core/project.py +31 -21
  105. novelwriter/core/projectdata.py +2 -21
  106. novelwriter/core/projectxml.py +13 -21
  107. novelwriter/core/sessions.py +2 -4
  108. novelwriter/core/spellcheck.py +12 -13
  109. novelwriter/core/status.py +27 -20
  110. novelwriter/core/storage.py +5 -10
  111. novelwriter/core/tree.py +6 -15
  112. novelwriter/dialogs/about.py +9 -10
  113. novelwriter/dialogs/docmerge.py +17 -14
  114. novelwriter/dialogs/docsplit.py +18 -14
  115. novelwriter/dialogs/editlabel.py +15 -9
  116. novelwriter/dialogs/preferences.py +69 -68
  117. novelwriter/dialogs/projectsettings.py +88 -67
  118. novelwriter/dialogs/quotes.py +15 -10
  119. novelwriter/dialogs/wordlist.py +18 -21
  120. novelwriter/enum.py +75 -30
  121. novelwriter/error.py +6 -11
  122. novelwriter/extensions/configlayout.py +8 -34
  123. novelwriter/extensions/eventfilters.py +3 -3
  124. novelwriter/extensions/modified.py +87 -32
  125. novelwriter/extensions/novelselector.py +13 -12
  126. novelwriter/extensions/pagedsidebar.py +10 -18
  127. novelwriter/extensions/progressbars.py +5 -11
  128. novelwriter/extensions/statusled.py +3 -6
  129. novelwriter/extensions/switch.py +8 -11
  130. novelwriter/extensions/switchbox.py +2 -11
  131. novelwriter/extensions/versioninfo.py +6 -7
  132. novelwriter/formats/shared.py +10 -2
  133. novelwriter/formats/todocx.py +15 -37
  134. novelwriter/formats/tohtml.py +52 -61
  135. novelwriter/formats/tokenizer.py +33 -64
  136. novelwriter/formats/tomarkdown.py +4 -11
  137. novelwriter/formats/toodt.py +12 -71
  138. novelwriter/formats/toqdoc.py +11 -21
  139. novelwriter/formats/toraw.py +2 -6
  140. novelwriter/gui/doceditor.py +207 -245
  141. novelwriter/gui/dochighlight.py +142 -101
  142. novelwriter/gui/docviewer.py +53 -84
  143. novelwriter/gui/docviewerpanel.py +18 -41
  144. novelwriter/gui/editordocument.py +12 -17
  145. novelwriter/gui/itemdetails.py +5 -14
  146. novelwriter/gui/mainmenu.py +24 -32
  147. novelwriter/gui/noveltree.py +13 -51
  148. novelwriter/gui/outline.py +20 -61
  149. novelwriter/gui/projtree.py +40 -96
  150. novelwriter/gui/search.py +9 -24
  151. novelwriter/gui/sidebar.py +54 -22
  152. novelwriter/gui/statusbar.py +7 -22
  153. novelwriter/gui/theme.py +482 -368
  154. novelwriter/guimain.py +87 -101
  155. novelwriter/shared.py +79 -48
  156. novelwriter/splash.py +9 -5
  157. novelwriter/text/comments.py +1 -1
  158. novelwriter/text/counting.py +9 -5
  159. novelwriter/text/patterns.py +20 -15
  160. novelwriter/tools/dictionaries.py +18 -16
  161. novelwriter/tools/lipsum.py +15 -17
  162. novelwriter/tools/manusbuild.py +25 -45
  163. novelwriter/tools/manuscript.py +94 -95
  164. novelwriter/tools/manussettings.py +149 -104
  165. novelwriter/tools/noveldetails.py +10 -24
  166. novelwriter/tools/welcome.py +24 -72
  167. novelwriter/tools/writingstats.py +17 -26
  168. novelwriter/types.py +25 -13
  169. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/METADATA +7 -7
  170. novelwriter-2.8b1.dist-info/RECORD +212 -0
  171. novelwriter/assets/images/welcome-dark.jpg +0 -0
  172. novelwriter/assets/images/welcome-light.jpg +0 -0
  173. novelwriter/assets/syntax/cyberpunk_night.conf +0 -28
  174. novelwriter/assets/syntax/default_dark.conf +0 -42
  175. novelwriter/assets/syntax/default_light.conf +0 -42
  176. novelwriter/assets/syntax/dracula.conf +0 -44
  177. novelwriter/assets/syntax/grey_dark.conf +0 -29
  178. novelwriter/assets/syntax/grey_light.conf +0 -29
  179. novelwriter/assets/syntax/light_owl.conf +0 -49
  180. novelwriter/assets/syntax/night_owl.conf +0 -49
  181. novelwriter/assets/syntax/snazzy.conf +0 -42
  182. novelwriter/assets/syntax/solarized_dark.conf +0 -29
  183. novelwriter/assets/syntax/solarized_light.conf +0 -29
  184. novelwriter/assets/syntax/tango.conf +0 -39
  185. novelwriter/assets/syntax/tomorrow.conf +0 -49
  186. novelwriter/assets/syntax/tomorrow_night.conf +0 -49
  187. novelwriter/assets/syntax/tomorrow_night_blue.conf +0 -49
  188. novelwriter/assets/syntax/tomorrow_night_bright.conf +0 -49
  189. novelwriter/assets/syntax/tomorrow_night_eighties.conf +0 -49
  190. novelwriter/assets/themes/default.conf +0 -3
  191. novelwriter-2.7.4.dist-info/RECORD +0 -163
  192. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/WHEEL +0 -0
  193. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/entry_points.txt +0 -0
  194. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/LICENSE.md +0 -0
  195. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/licenses/setup/LICENSE-Apache-2.0.txt +0 -0
  196. {novelwriter-2.7.4.dist-info → novelwriter-2.8b1.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  from typing import TYPE_CHECKING
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
39
39
 
40
40
 
41
41
  class NSwitchBox(QScrollArea):
42
- """Extension: Switch Box Widget
42
+ """Extension: Switch Box Widget.
43
43
 
44
44
  A widget that can hold a list of switches with labels and optional
45
45
  icons. The switch toggles emits a common signal with a switch key.
@@ -55,7 +55,6 @@ class NSwitchBox(QScrollArea):
55
55
  self._sIcon = baseSize
56
56
  self._widgets = []
57
57
  self.clear()
58
- return
59
58
 
60
59
  def clear(self) -> None:
61
60
  """Rebuild the content of the core widget."""
@@ -72,8 +71,6 @@ class NSwitchBox(QScrollArea):
72
71
  self.setWidgetResizable(True)
73
72
  self.setWidget(self._widget)
74
73
 
75
- return
76
-
77
74
  def addLabel(self, text: str) -> None:
78
75
  """Add a header label to the content box."""
79
76
  label = QLabel(text, self)
@@ -83,7 +80,6 @@ class NSwitchBox(QScrollArea):
83
80
  self._content.addWidget(label, self._index, 0, 1, 3, QtAlignLeft)
84
81
  self._widgets.append(label)
85
82
  self._bumpIndex()
86
- return
87
83
 
88
84
  def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False) -> None:
89
85
  """Add an item to the content box."""
@@ -104,8 +100,6 @@ class NSwitchBox(QScrollArea):
104
100
  self._widgets.append(switch)
105
101
  self._bumpIndex()
106
102
 
107
- return
108
-
109
103
  def addSeparator(self) -> None:
110
104
  """Add a blank entry in the content box."""
111
105
  spacer = QWidget(self)
@@ -113,7 +107,6 @@ class NSwitchBox(QScrollArea):
113
107
  self._content.addWidget(spacer, self._index, 0, 1, 3, QtAlignLeft)
114
108
  self._widgets.append(spacer)
115
109
  self._bumpIndex()
116
- return
117
110
 
118
111
  ##
119
112
  # Internal Functions
@@ -122,7 +115,6 @@ class NSwitchBox(QScrollArea):
122
115
  def _emitSwitchSignal(self, identifier: str, state: bool) -> None:
123
116
  """Emit a signal for a switch toggle."""
124
117
  self.switchToggled.emit(identifier, state)
125
- return
126
118
 
127
119
  def _bumpIndex(self) -> None:
128
120
  """Increase the index counter and make sure only the last
@@ -131,4 +123,3 @@ class NSwitchBox(QScrollArea):
131
123
  self._content.setRowStretch(self._index, 0)
132
124
  self._content.setRowStretch(self._index + 1, 1)
133
125
  self._index += 1
134
- return
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import json
@@ -45,6 +45,11 @@ API_URL = "https://api.github.com/repos/vkbo/novelwriter/releases/latest"
45
45
 
46
46
 
47
47
  class VersionInfoWidget(QWidget):
48
+ """Custom: version Info Label.
49
+
50
+ A custom widget that will show a clickable area for contacting
51
+ GitHub and pulling the latest release version info.
52
+ """
48
53
 
49
54
  def __init__(self, parent: QWidget) -> None:
50
55
  super().__init__(parent=parent)
@@ -75,8 +80,6 @@ class VersionInfoWidget(QWidget):
75
80
 
76
81
  self.setLayout(self._layout)
77
82
 
78
- return
79
-
80
83
  ##
81
84
  # Private Slots
82
85
  ##
@@ -93,7 +96,6 @@ class VersionInfoWidget(QWidget):
93
96
  lookup = _Retriever()
94
97
  lookup.signals.dataReady.connect(self._updateReleaseInfo)
95
98
  SHARED.runInThreadPool(lookup)
96
- return
97
99
 
98
100
  ##
99
101
  # Private Slots
@@ -109,7 +111,6 @@ class VersionInfoWidget(QWidget):
109
111
  ))
110
112
  else:
111
113
  self._lblRelease.setText(self._trLatest.format(reason or self.tr("Failed")))
112
- return
113
114
 
114
115
 
115
116
  class _Retriever(QRunnable):
@@ -117,7 +118,6 @@ class _Retriever(QRunnable):
117
118
  def __init__(self) -> None:
118
119
  super().__init__()
119
120
  self.signals = _RetrieverSignal()
120
- return
121
121
 
122
122
  @pyqtSlot()
123
123
  def run(self) -> None:
@@ -140,7 +140,6 @@ class _Retriever(QRunnable):
140
140
  except Exception as e:
141
141
  logger.error("Failed to retrieve release info")
142
142
  self.signals.dataReady.emit("", str(e))
143
- return
144
143
 
145
144
 
146
145
  class _RetrieverSignal(QObject):
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import re
@@ -29,7 +29,15 @@ from enum import Flag, IntEnum
29
29
 
30
30
  from PyQt6.QtGui import QColor
31
31
 
32
- ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
32
+ ESCAPES = {
33
+ r"\*": "*",
34
+ r"\~": "~",
35
+ r"\=": "=",
36
+ r"\_": "_",
37
+ r"\[": "[",
38
+ r"\]": "]",
39
+ r"\ ": "",
40
+ }
33
41
  RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
34
42
 
35
43
 
@@ -21,7 +21,7 @@ General Public License for more details.
21
21
 
22
22
  You should have received a copy of the GNU General Public License
23
23
  along with this program. If not, see <https://www.gnu.org/licenses/>.
24
- """
24
+ """ # noqa
25
25
  from __future__ import annotations
26
26
 
27
27
  import logging
@@ -38,7 +38,7 @@ from novelwriter import __version__
38
38
  from novelwriter.common import firstFloat, xmlElement, xmlSubElem
39
39
  from novelwriter.constants import nwHeadFmt, nwStyles
40
40
  from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
41
- from novelwriter.formats.tokenizer import Tokenizer
41
+ from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
42
42
  from novelwriter.types import QtHexRgb
43
43
 
44
44
  if TYPE_CHECKING:
@@ -51,7 +51,7 @@ if TYPE_CHECKING:
51
51
  logger = logging.getLogger(__name__)
52
52
 
53
53
  # RegEx
54
- RX_TEXT = re.compile(r"([\n\t])", re.UNICODE)
54
+ RX_TEXT = re.compile(r"([\n\t])")
55
55
 
56
56
  # Types and Relationships
57
57
  OOXML_SCM = "http://schemas.openxmlformats.org"
@@ -100,7 +100,7 @@ def _wText(parent: ET.Element, text: str) -> ET.Element:
100
100
 
101
101
 
102
102
  def _mmToSz(value: float) -> int:
103
- """Convert millimetres to internal margin size units"""
103
+ """Convert millimetres to internal margin size units."""
104
104
  return int(value*20.0*72.0/25.4)
105
105
 
106
106
 
@@ -143,6 +143,7 @@ S_FNOTE = "FootnoteText"
143
143
 
144
144
 
145
145
  class DocXXmlRel(NamedTuple):
146
+ """DocX XML Rel Data."""
146
147
 
147
148
  rId: str
148
149
  relType: str
@@ -150,6 +151,7 @@ class DocXXmlRel(NamedTuple):
150
151
 
151
152
 
152
153
  class DocXXmlFile(NamedTuple):
154
+ """DocX XML File Data."""
153
155
 
154
156
  xml: ET.Element
155
157
  path: str
@@ -157,6 +159,7 @@ class DocXXmlFile(NamedTuple):
157
159
 
158
160
 
159
161
  class DocXParStyle(NamedTuple):
162
+ """DocX XML Paragraph Style Data."""
160
163
 
161
164
  name: str
162
165
  styleId: str
@@ -176,7 +179,7 @@ class DocXParStyle(NamedTuple):
176
179
 
177
180
 
178
181
  class ToDocX(Tokenizer):
179
- """Core: DocX Document Writer
182
+ """Core: DocX Document Writer.
180
183
 
181
184
  Extend the Tokenizer class to writer DocX Document files.
182
185
  """
@@ -202,8 +205,6 @@ class ToDocX(Tokenizer):
202
205
  self._usedNotes: dict[str, int] = {}
203
206
  self._usedFields: list[tuple[ET.Element, str]] = []
204
207
 
205
- return
206
-
207
208
  ##
208
209
  # Setters
209
210
  ##
@@ -214,25 +215,22 @@ class ToDocX(Tokenizer):
214
215
  """Set the document page size and margins in millimetres."""
215
216
  self._pageSize = QSize(_mmToSz(width), _mmToSz(height))
216
217
  self._pageMargins = QMargins(_mmToSz(left), _mmToSz(top), _mmToSz(right), _mmToSz(bottom))
217
- return
218
218
 
219
219
  def setHeaderFormat(self, value: str, offset: int) -> None:
220
220
  """Set the document header format."""
221
221
  self._headerFormat = value.strip()
222
222
  self._pageOffset = offset
223
- return
224
223
 
225
224
  ##
226
225
  # Class Methods
227
226
  ##
228
227
 
229
228
  def initDocument(self) -> None:
230
- """Initialises the DocX document structure."""
229
+ """Initialise the DocX document structure."""
231
230
  super().initDocument()
232
231
  self._fontFamily = self._textFont.family()
233
232
  self._fontSize = self._textFont.pointSizeF()
234
233
  self._generateStyles()
235
- return
236
234
 
237
235
  def doConvert(self) -> None:
238
236
  """Convert the list of text tokens into XML elements."""
@@ -296,14 +294,12 @@ class ToDocX(Tokenizer):
296
294
  elif tType == BlockTyp.SKIP:
297
295
  self._processFragments(par, S_NORM, "")
298
296
 
299
- elif tType == BlockTyp.COMMENT:
297
+ elif tType in COMMENT_BLOCKS:
300
298
  self._processFragments(par, S_META, tText, tFormat)
301
299
 
302
300
  elif tType == BlockTyp.KEYWORD:
303
301
  self._processFragments(par, S_META, tText, tFormat)
304
302
 
305
- return
306
-
307
303
  def closeDocument(self) -> None:
308
304
  """Generate all the XML."""
309
305
  self._coreXml()
@@ -322,8 +318,6 @@ class ToDocX(Tokenizer):
322
318
  if self._usedNotes:
323
319
  self._footnotesXml()
324
320
 
325
- return
326
-
327
321
  def saveDocument(self, path: Path) -> None:
328
322
  """Save the data to a .docx file."""
329
323
  # Content Lists
@@ -373,8 +367,6 @@ class ToDocX(Tokenizer):
373
367
  xmlToZip(f"{rel.path}/{name}", rel.xml, outZip)
374
368
  xmlToZip("[Content_Types].xml", dTypes, outZip)
375
369
 
376
- return
377
-
378
370
  ##
379
371
  # Internal Functions
380
372
  ##
@@ -454,8 +446,6 @@ class ToDocX(Tokenizer):
454
446
  if temp := text[fStart:]:
455
447
  par.addContent(self._textRunToXml(temp, xFmt, fClass, fLink))
456
448
 
457
- return
458
-
459
449
  def _textRunToXml(self, text: str | None, fmt: int, fClass: str, fLink: str) -> ET.Element:
460
450
  """Encode the text run into XML."""
461
451
  xR = xmlElement(_wTag("r"))
@@ -668,8 +658,6 @@ class ToDocX(Tokenizer):
668
658
  for style in styles:
669
659
  self._styles[style.styleId] = style
670
660
 
671
- return
672
-
673
661
  def _nextRelId(self) -> str:
674
662
  """Generate the next unique rId."""
675
663
  return f"rId{len(self._rels) + 1}"
@@ -1054,6 +1042,10 @@ class ToDocX(Tokenizer):
1054
1042
 
1055
1043
 
1056
1044
  class DocXParagraph:
1045
+ """DocX Text Paragraph.
1046
+
1047
+ This class holds a single paragraph of a DocX document.
1048
+ """
1057
1049
 
1058
1050
  __slots__ = (
1059
1051
  "_bottomMargin", "_breakAfter", "_breakBefore", "_content",
@@ -1073,7 +1065,6 @@ class DocXParagraph:
1073
1065
  self._breakBefore = False
1074
1066
  self._breakAfter = False
1075
1067
  self._footnoteRef = False
1076
- return
1077
1068
 
1078
1069
  ##
1079
1070
  # Properties
@@ -1091,53 +1082,43 @@ class DocXParagraph:
1091
1082
  def setStyle(self, style: DocXParStyle | None) -> None:
1092
1083
  """Set the paragraph style."""
1093
1084
  self._style = style
1094
- return
1095
1085
 
1096
1086
  def setAlignment(self, value: str) -> None:
1097
1087
  """Set paragraph alignment."""
1098
1088
  if value in ("left", "center", "right", "both"):
1099
1089
  self._textAlign = value
1100
- return
1101
1090
 
1102
1091
  def setMarginTop(self, value: float) -> None:
1103
1092
  """Set margin above in pt."""
1104
1093
  self._topMargin = value
1105
- return
1106
1094
 
1107
1095
  def setMarginBottom(self, value: float) -> None:
1108
1096
  """Set margin below in pt."""
1109
1097
  self._bottomMargin = value
1110
- return
1111
1098
 
1112
1099
  def setMarginLeft(self, value: float) -> None:
1113
1100
  """Set margin left in pt."""
1114
1101
  self._leftMargin = value
1115
- return
1116
1102
 
1117
1103
  def setMarginRight(self, value: float) -> None:
1118
1104
  """Set margin right in pt."""
1119
1105
  self._rightMargin = value
1120
- return
1121
1106
 
1122
1107
  def setIndentFirst(self, state: bool) -> None:
1123
1108
  """Set first line indent."""
1124
1109
  self._indentFirst = state
1125
- return
1126
1110
 
1127
1111
  def setPageBreakBefore(self, state: bool) -> None:
1128
1112
  """Set page break before flag."""
1129
1113
  self._breakBefore = state
1130
- return
1131
1114
 
1132
1115
  def setPageBreakAfter(self, state: bool) -> None:
1133
1116
  """Set page break after flag."""
1134
1117
  self._breakAfter = state
1135
- return
1136
1118
 
1137
1119
  def setIsFootnote(self, state: bool) -> None:
1138
1120
  """Set is footnote flag."""
1139
1121
  self._footnoteRef = state
1140
- return
1141
1122
 
1142
1123
  ##
1143
1124
  # Methods
@@ -1146,10 +1127,9 @@ class DocXParagraph:
1146
1127
  def addContent(self, run: ET.Element) -> None:
1147
1128
  """Add a run segment to the paragraph."""
1148
1129
  self._content.append(run)
1149
- return
1150
1130
 
1151
1131
  def toXml(self, body: ET.Element) -> None:
1152
- """Called after all content is set."""
1132
+ """Generate the XML. Call after all content is set."""
1153
1133
  if style := self._style:
1154
1134
  xP = xmlSubElem(body, _wTag("p"))
1155
1135
 
@@ -1191,5 +1171,3 @@ class DocXParagraph:
1191
1171
  if self._breakAfter:
1192
1172
  xR = xmlSubElem(xP, _wTag("r"))
1193
1173
  xmlSubElem(xR, _wTag("br"), attrib={_wTag("type"): "page"})
1194
-
1195
- return
@@ -20,7 +20,7 @@ General Public License for more details.
20
20
 
21
21
  You should have received a copy of the GNU General Public License
22
22
  along with this program. If not, see <https://www.gnu.org/licenses/>.
23
- """
23
+ """ # noqa
24
24
  from __future__ import annotations
25
25
 
26
26
  import json
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING
32
32
  from novelwriter.common import formatTimeStamp
33
33
  from novelwriter.constants import nwHtmlUnicode, nwStyles
34
34
  from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
35
- from novelwriter.formats.tokenizer import Tokenizer
35
+ from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
36
36
  from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
37
37
 
38
38
  if TYPE_CHECKING:
@@ -77,7 +77,7 @@ HTML_NONE = (0, "")
77
77
 
78
78
 
79
79
  class ToHtml(Tokenizer):
80
- """Core: HTML Document Writer
80
+ """Core: HTML Document Writer.
81
81
 
82
82
  Extend the Tokenizer class to writer HTML output. This class is
83
83
  also used by the Document Viewer, and Manuscript Build Preview.
@@ -90,7 +90,6 @@ class ToHtml(Tokenizer):
90
90
  self._usedNotes: dict[str, int] = {}
91
91
  self._usedFields: list[tuple[int, str]] = []
92
92
  self.setReplaceUnicode(False)
93
- return
94
93
 
95
94
  ##
96
95
  # Setters
@@ -101,7 +100,6 @@ class ToHtml(Tokenizer):
101
100
  class tags.
102
101
  """
103
102
  self._cssStyles = cssStyles
104
- return
105
103
 
106
104
  def setReplaceUnicode(self, doReplace: bool) -> None:
107
105
  """Set the translation map to either minimal or full unicode for
@@ -114,7 +112,6 @@ class ToHtml(Tokenizer):
114
112
  if doReplace:
115
113
  # Extend to all relevant Unicode characters
116
114
  self._trMap.update(str.maketrans(nwHtmlUnicode.U_TO_H))
117
- return
118
115
 
119
116
  ##
120
117
  # Class Methods
@@ -130,7 +127,6 @@ class ToHtml(Tokenizer):
130
127
  """
131
128
  super().doPreProcessing()
132
129
  self._text = self._text.translate(self._trMap)
133
- return
134
130
 
135
131
  def doConvert(self) -> None:
136
132
  """Convert the list of text tokens into an HTML document."""
@@ -159,34 +155,33 @@ class ToHtml(Tokenizer):
159
155
  # If we don't have formatting, we can do a plain replace
160
156
  tText = tText.replace("<", "&lt;").replace(">", "&gt;")
161
157
 
162
- # Styles
158
+ # Inline Styles
163
159
  aStyle = []
164
- if self._cssStyles:
165
- if tStyle & BlockFmt.LEFT:
166
- aStyle.append("text-align: left;")
167
- elif tStyle & BlockFmt.RIGHT:
168
- aStyle.append("text-align: right;")
169
- elif tStyle & BlockFmt.CENTRE:
170
- aStyle.append("text-align: center;")
171
- elif tStyle & BlockFmt.JUSTIFY:
172
- aStyle.append("text-align: justify;")
173
-
174
- if tStyle & BlockFmt.PBB:
175
- aStyle.append("page-break-before: always;")
176
- if tStyle & BlockFmt.PBA:
177
- aStyle.append("page-break-after: always;")
178
-
179
- if tStyle & BlockFmt.Z_BTM:
180
- aStyle.append("margin-bottom: 0;")
181
- if tStyle & BlockFmt.Z_TOP:
182
- aStyle.append("margin-top: 0;")
183
-
184
- if tStyle & BlockFmt.IND_L:
185
- aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
186
- if tStyle & BlockFmt.IND_R:
187
- aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
188
- if tStyle & BlockFmt.IND_T:
189
- aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
160
+ if tStyle & BlockFmt.LEFT:
161
+ aStyle.append("text-align: left;")
162
+ elif tStyle & BlockFmt.RIGHT:
163
+ aStyle.append("text-align: right;")
164
+ elif tStyle & BlockFmt.CENTRE:
165
+ aStyle.append("text-align: center;")
166
+ elif tStyle & BlockFmt.JUSTIFY:
167
+ aStyle.append("text-align: justify;")
168
+
169
+ if tStyle & BlockFmt.PBB:
170
+ aStyle.append("page-break-before: always;")
171
+ if tStyle & BlockFmt.PBA:
172
+ aStyle.append("page-break-after: always;")
173
+
174
+ if tStyle & BlockFmt.Z_BTM:
175
+ aStyle.append("margin-bottom: 0;")
176
+ if tStyle & BlockFmt.Z_TOP:
177
+ aStyle.append("margin-top: 0;")
178
+
179
+ if tStyle & BlockFmt.IND_L:
180
+ aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
181
+ if tStyle & BlockFmt.IND_R:
182
+ aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
183
+ if tStyle & BlockFmt.IND_T:
184
+ aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
190
185
 
191
186
  if aStyle:
192
187
  stVals = " ".join(aStyle)
@@ -229,7 +224,7 @@ class ToHtml(Tokenizer):
229
224
  elif tType == BlockTyp.SKIP:
230
225
  lines.append(f"<p{hStyle}>&nbsp;</p>\n")
231
226
 
232
- elif tType == BlockTyp.COMMENT:
227
+ elif tType in COMMENT_BLOCKS:
233
228
  lines.append(f"<p class='comment'{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
234
229
 
235
230
  elif tType == BlockTyp.KEYWORD:
@@ -238,8 +233,6 @@ class ToHtml(Tokenizer):
238
233
 
239
234
  self._pages.append("".join(lines))
240
235
 
241
- return
242
-
243
236
  def closeDocument(self) -> None:
244
237
  """Run close document tasks."""
245
238
  # Replace fields if there are stats available
@@ -266,8 +259,6 @@ class ToHtml(Tokenizer):
266
259
 
267
260
  self._pages.append("".join(lines))
268
261
 
269
- return
270
-
271
262
  def saveDocument(self, path: Path) -> None:
272
263
  """Save the data to an HTML file."""
273
264
  if path.suffix.lower() == ".json":
@@ -288,37 +279,33 @@ class ToHtml(Tokenizer):
288
279
  json.dump(data, fObj, indent=2)
289
280
 
290
281
  else:
282
+ html = []
283
+ html.append("<!DOCTYPE html>")
284
+ html.append("<html>")
285
+ html.append("<head>")
286
+ html.append(f"<title>{self._project.data.name:s}</title>")
287
+ html.append("<meta charset='utf-8'>")
288
+ if self._cssStyles:
289
+ html.append("<meta name='viewport' content='width=device-width, initial-scale=1'>")
290
+ html.append("<style>")
291
+ html.extend(self.getStyleSheet())
292
+ html.append("</style>")
293
+ html.append("</head>")
294
+ html.append("<body>")
295
+ html.append(("".join(self._pages)).replace("\t", "&#09;").rstrip())
296
+ html.append("</body>")
297
+ html.append("</html>\n")
298
+
291
299
  with open(path, mode="w", encoding="utf-8") as fObj:
292
- fObj.write((
293
- "<!DOCTYPE html>\n"
294
- "<html>\n"
295
- "<head>\n"
296
- "<meta charset='utf-8'>\n"
297
- "<title>{title:s}</title>\n"
298
- "<style>\n"
299
- "{style:s}\n"
300
- "</style>\n"
301
- "</head>\n"
302
- "<body>\n"
303
- "{body:s}\n"
304
- "</body>\n"
305
- "</html>\n"
306
- ).format(
307
- title=self._project.data.name,
308
- style="\n".join(self.getStyleSheet()),
309
- body=("".join(self._pages)).replace("\t", "&#09;").rstrip(),
310
- ))
300
+ fObj.write("\n".join(html))
311
301
 
312
302
  logger.info("Wrote file: %s", path)
313
303
 
314
- return
315
-
316
304
  def replaceTabs(self, nSpaces: int = 8, spaceChar: str = "&nbsp;") -> None:
317
305
  """Replace tabs with spaces in the html."""
318
306
  tabSpace = spaceChar*nSpaces
319
307
  pages = [aLine.replace("\t", tabSpace) for aLine in self._pages]
320
308
  self._pages = pages
321
- return
322
309
 
323
310
  def getStyleSheet(self) -> list[str]:
324
311
  """Generate a stylesheet for the current settings."""
@@ -410,7 +397,11 @@ class ToHtml(Tokenizer):
410
397
  # isn't already open, and only closed if it has previously been opened.
411
398
  tags: list[tuple[int, str]] = []
412
399
  state = dict.fromkeys(HTML_OPENER, False)
400
+ plain = not self._cssStyles
413
401
  for pos, fmt, data in tFmt:
402
+ if plain and fmt in (TextFmt.COL_B, TextFmt.COL_E):
403
+ # We ignore colour tags if CSS is off
404
+ continue
414
405
  if m := HTML_OPENER.get(fmt):
415
406
  if not state.get(fmt, True):
416
407
  if fmt == TextFmt.COL_B and (color := self._classes.get(data)):