novelWriter 2.4.3__py3-none-any.whl → 2.5__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.
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/METADATA +4 -5
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/RECORD +122 -112
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/i18n/project_pl_PL.json +116 -0
- novelwriter/assets/i18n/project_pt_BR.json +74 -74
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +52 -41
- novelwriter/assets/themes/cyberpunk_night.conf +3 -0
- novelwriter/assets/themes/default_dark.conf +2 -0
- novelwriter/assets/themes/default_light.conf +2 -0
- novelwriter/assets/themes/dracula.conf +48 -0
- novelwriter/assets/themes/solarized_dark.conf +2 -0
- novelwriter/assets/themes/solarized_light.conf +2 -0
- novelwriter/common.py +33 -12
- novelwriter/config.py +184 -98
- novelwriter/constants.py +47 -35
- novelwriter/core/buildsettings.py +68 -69
- novelwriter/core/coretools.py +5 -23
- novelwriter/core/docbuild.py +52 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +37 -61
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +139 -105
- novelwriter/core/tokenizer.py +278 -122
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +257 -166
- novelwriter/core/toqdoc.py +419 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +11 -18
- novelwriter/dialogs/docmerge.py +17 -19
- novelwriter/dialogs/docsplit.py +17 -19
- novelwriter/dialogs/editlabel.py +6 -10
- novelwriter/dialogs/preferences.py +200 -164
- novelwriter/dialogs/projectsettings.py +225 -189
- novelwriter/dialogs/quotes.py +12 -9
- novelwriter/dialogs/wordlist.py +9 -15
- novelwriter/enum.py +35 -30
- novelwriter/error.py +8 -15
- novelwriter/extensions/configlayout.py +55 -21
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +70 -14
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/{circularprogress.py → progressbars.py} +30 -8
- novelwriter/extensions/statusled.py +40 -26
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +120 -139
- novelwriter/gui/dochighlight.py +231 -186
- novelwriter/gui/docviewer.py +69 -108
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +22 -18
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +15 -26
- novelwriter/gui/projtree.py +39 -65
- novelwriter/gui/search.py +10 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +29 -37
- novelwriter/gui/theme.py +26 -48
- novelwriter/guimain.py +162 -160
- novelwriter/shared.py +36 -19
- novelwriter/text/patterns.py +113 -0
- novelwriter/tools/dictionaries.py +10 -20
- novelwriter/tools/lipsum.py +10 -16
- novelwriter/tools/manusbuild.py +9 -11
- novelwriter/tools/manuscript.py +75 -149
- novelwriter/tools/manussettings.py +74 -76
- novelwriter/tools/noveldetails.py +16 -21
- novelwriter/tools/welcome.py +21 -26
- novelwriter/tools/writingstats.py +9 -12
- novelwriter/types.py +49 -4
- novelwriter/extensions/simpleprogress.py +0 -55
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5.dist-info}/top_level.txt +0 -0
novelwriter/core/tohtml.py
CHANGED
@@ -26,17 +26,46 @@ from __future__ import annotations
|
|
26
26
|
import json
|
27
27
|
import logging
|
28
28
|
|
29
|
-
from time import time
|
30
29
|
from pathlib import Path
|
30
|
+
from time import time
|
31
31
|
|
32
|
-
from novelwriter import CONFIG
|
33
32
|
from novelwriter.common import formatTimeStamp
|
34
|
-
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels
|
33
|
+
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels
|
35
34
|
from novelwriter.core.project import NWProject
|
36
|
-
from novelwriter.core.tokenizer import Tokenizer, stripEscape
|
35
|
+
from novelwriter.core.tokenizer import T_Formats, Tokenizer, stripEscape
|
36
|
+
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
|
37
37
|
|
38
38
|
logger = logging.getLogger(__name__)
|
39
39
|
|
40
|
+
# Each opener tag, with the id of its corresponding closer and tag format
|
41
|
+
HTML_OPENER: dict[int, tuple[int, str]] = {
|
42
|
+
Tokenizer.FMT_B_B: (Tokenizer.FMT_B_E, "<strong>"),
|
43
|
+
Tokenizer.FMT_I_B: (Tokenizer.FMT_I_E, "<em>"),
|
44
|
+
Tokenizer.FMT_D_B: (Tokenizer.FMT_D_E, "<del>"),
|
45
|
+
Tokenizer.FMT_U_B: (Tokenizer.FMT_U_E, "<span style='text-decoration: underline;'>"),
|
46
|
+
Tokenizer.FMT_M_B: (Tokenizer.FMT_M_E, "<mark>"),
|
47
|
+
Tokenizer.FMT_SUP_B: (Tokenizer.FMT_SUP_E, "<sup>"),
|
48
|
+
Tokenizer.FMT_SUB_B: (Tokenizer.FMT_SUB_E, "<sub>"),
|
49
|
+
Tokenizer.FMT_DL_B: (Tokenizer.FMT_DL_E, "<span class='dialog'>"),
|
50
|
+
Tokenizer.FMT_ADL_B: (Tokenizer.FMT_ADL_E, "<span class='altdialog'>"),
|
51
|
+
}
|
52
|
+
|
53
|
+
# Each closer tag, with the id of its corresponding opener and tag format
|
54
|
+
HTML_CLOSER: dict[int, tuple[int, str]] = {
|
55
|
+
Tokenizer.FMT_B_E: (Tokenizer.FMT_B_B, "</strong>"),
|
56
|
+
Tokenizer.FMT_I_E: (Tokenizer.FMT_I_B, "</em>"),
|
57
|
+
Tokenizer.FMT_D_E: (Tokenizer.FMT_D_B, "</del>"),
|
58
|
+
Tokenizer.FMT_U_E: (Tokenizer.FMT_U_B, "</span>"),
|
59
|
+
Tokenizer.FMT_M_E: (Tokenizer.FMT_M_B, "</mark>"),
|
60
|
+
Tokenizer.FMT_SUP_E: (Tokenizer.FMT_SUP_B, "</sup>"),
|
61
|
+
Tokenizer.FMT_SUB_E: (Tokenizer.FMT_SUB_B, "</sub>"),
|
62
|
+
Tokenizer.FMT_DL_E: (Tokenizer.FMT_DL_B, "</span>"),
|
63
|
+
Tokenizer.FMT_ADL_E: (Tokenizer.FMT_ADL_B, "</span>"),
|
64
|
+
}
|
65
|
+
|
66
|
+
# Empty HTML tag record
|
67
|
+
HTML_NONE = (0, "")
|
68
|
+
|
40
69
|
|
41
70
|
class ToHtml(Tokenizer):
|
42
71
|
"""Core: HTML Document Writer
|
@@ -45,19 +74,15 @@ class ToHtml(Tokenizer):
|
|
45
74
|
also used by the Document Viewer, and Manuscript Build Preview.
|
46
75
|
"""
|
47
76
|
|
48
|
-
M_PREVIEW = 0 # Tweak output for the DocViewer
|
49
|
-
M_EXPORT = 1 # Tweak output for saving to HTML or printing
|
50
|
-
M_EBOOK = 2 # Tweak output for converting to epub
|
51
|
-
|
52
77
|
def __init__(self, project: NWProject) -> None:
|
53
78
|
super().__init__(project)
|
54
79
|
|
55
|
-
self._genMode = self.M_EXPORT
|
56
80
|
self._cssStyles = True
|
57
81
|
self._fullHTML: list[str] = []
|
58
82
|
|
59
83
|
# Internals
|
60
84
|
self._trMap = {}
|
85
|
+
self._usedNotes: dict[str, int] = {}
|
61
86
|
self.setReplaceUnicode(False)
|
62
87
|
|
63
88
|
return
|
@@ -74,11 +99,6 @@ class ToHtml(Tokenizer):
|
|
74
99
|
# Setters
|
75
100
|
##
|
76
101
|
|
77
|
-
def setPreview(self, state: bool) -> None:
|
78
|
-
"""Set to preview generator mode."""
|
79
|
-
self._genMode = self.M_PREVIEW if state else self.M_EXPORT
|
80
|
-
return
|
81
|
-
|
82
102
|
def setStyles(self, cssStyles: bool) -> None:
|
83
103
|
"""Enable or disable CSS styling. Some elements may still have
|
84
104
|
class tags.
|
@@ -117,39 +137,9 @@ class ToHtml(Tokenizer):
|
|
117
137
|
|
118
138
|
def doConvert(self) -> None:
|
119
139
|
"""Convert the list of text tokens into an HTML document."""
|
120
|
-
|
121
|
-
htmlTags = { # HTML4 + CSS2 (for Qt)
|
122
|
-
self.FMT_B_B: "<b>",
|
123
|
-
self.FMT_B_E: "</b>",
|
124
|
-
self.FMT_I_B: "<i>",
|
125
|
-
self.FMT_I_E: "</i>",
|
126
|
-
self.FMT_D_B: "<span style='text-decoration: line-through;'>",
|
127
|
-
self.FMT_D_E: "</span>",
|
128
|
-
self.FMT_U_B: "<u>",
|
129
|
-
self.FMT_U_E: "</u>",
|
130
|
-
self.FMT_M_B: "<mark>",
|
131
|
-
self.FMT_M_E: "</mark>",
|
132
|
-
}
|
133
|
-
else:
|
134
|
-
htmlTags = { # HTML5 (for export)
|
135
|
-
self.FMT_B_B: "<strong>",
|
136
|
-
self.FMT_B_E: "</strong>",
|
137
|
-
self.FMT_I_B: "<em>",
|
138
|
-
self.FMT_I_E: "</em>",
|
139
|
-
self.FMT_D_B: "<del>",
|
140
|
-
self.FMT_D_E: "</del>",
|
141
|
-
self.FMT_U_B: "<span style='text-decoration: underline;'>",
|
142
|
-
self.FMT_U_E: "</span>",
|
143
|
-
self.FMT_M_B: "<mark>",
|
144
|
-
self.FMT_M_E: "</mark>",
|
145
|
-
}
|
146
|
-
|
147
|
-
htmlTags[self.FMT_SUP_B] = "<sup>"
|
148
|
-
htmlTags[self.FMT_SUP_E] = "</sup>"
|
149
|
-
htmlTags[self.FMT_SUB_B] = "<sub>"
|
150
|
-
htmlTags[self.FMT_SUB_E] = "</sub>"
|
140
|
+
self._result = ""
|
151
141
|
|
152
|
-
if self._isNovel
|
142
|
+
if self._isNovel:
|
153
143
|
# For story files, we bump the titles one level up
|
154
144
|
h1Cl = " class='title'"
|
155
145
|
h1 = "h1"
|
@@ -163,12 +153,7 @@ class ToHtml(Tokenizer):
|
|
163
153
|
h3 = "h3"
|
164
154
|
h4 = "h4"
|
165
155
|
|
166
|
-
self._result = ""
|
167
|
-
|
168
|
-
para = []
|
169
|
-
pStyle = None
|
170
156
|
lines = []
|
171
|
-
|
172
157
|
tHandle = self._handle
|
173
158
|
|
174
159
|
for tType, nHead, tText, tFormat, tStyle in self._tokens:
|
@@ -181,11 +166,11 @@ class ToHtml(Tokenizer):
|
|
181
166
|
for c in tText:
|
182
167
|
if c == "<":
|
183
168
|
cText.append("<")
|
184
|
-
tFormat = [
|
169
|
+
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
|
185
170
|
i += 4
|
186
171
|
elif c == ">":
|
187
172
|
cText.append(">")
|
188
|
-
tFormat = [
|
173
|
+
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
|
189
174
|
i += 4
|
190
175
|
else:
|
191
176
|
cText.append(c)
|
@@ -209,7 +194,6 @@ class ToHtml(Tokenizer):
|
|
209
194
|
|
210
195
|
if tStyle & self.A_PBB:
|
211
196
|
aStyle.append("page-break-before: always;")
|
212
|
-
|
213
197
|
if tStyle & self.A_PBA:
|
214
198
|
aStyle.append("page-break-after: always;")
|
215
199
|
|
@@ -219,11 +203,13 @@ class ToHtml(Tokenizer):
|
|
219
203
|
aStyle.append("margin-top: 0;")
|
220
204
|
|
221
205
|
if tStyle & self.A_IND_L:
|
222
|
-
aStyle.append(f"margin-left: {
|
206
|
+
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
|
223
207
|
if tStyle & self.A_IND_R:
|
224
|
-
aStyle.append(f"margin-right: {
|
208
|
+
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
|
209
|
+
if tStyle & self.A_IND_T:
|
210
|
+
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
|
225
211
|
|
226
|
-
if
|
212
|
+
if aStyle:
|
227
213
|
stVals = " ".join(aStyle)
|
228
214
|
hStyle = f" style='{stVals}'"
|
229
215
|
else:
|
@@ -235,37 +221,27 @@ class ToHtml(Tokenizer):
|
|
235
221
|
aNm = ""
|
236
222
|
|
237
223
|
# Process Text Type
|
238
|
-
if tType == self.
|
239
|
-
|
240
|
-
pStyle = ""
|
241
|
-
if len(para) > 1 and self._cssStyles:
|
242
|
-
pClass = " class='break'"
|
243
|
-
else:
|
244
|
-
pClass = ""
|
245
|
-
if len(para) > 0:
|
246
|
-
tTemp = "<br/>".join(para)
|
247
|
-
lines.append(f"<p{pClass+pStyle}>{tTemp.rstrip()}</p>\n")
|
248
|
-
para = []
|
249
|
-
pStyle = None
|
224
|
+
if tType == self.T_TEXT:
|
225
|
+
lines.append(f"<p{hStyle}>{self._formatText(tText, tFormat)}</p>\n")
|
250
226
|
|
251
227
|
elif tType == self.T_TITLE:
|
252
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
228
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
253
229
|
lines.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
|
254
230
|
|
255
231
|
elif tType == self.T_HEAD1:
|
256
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
232
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
257
233
|
lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
|
258
234
|
|
259
235
|
elif tType == self.T_HEAD2:
|
260
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
236
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
261
237
|
lines.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
|
262
238
|
|
263
239
|
elif tType == self.T_HEAD3:
|
264
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
240
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
265
241
|
lines.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
|
266
242
|
|
267
243
|
elif tType == self.T_HEAD4:
|
268
|
-
tHead = tText.replace(nwHeadFmt.BR, "<br
|
244
|
+
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
269
245
|
lines.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
|
270
246
|
|
271
247
|
elif tType == self.T_SEP:
|
@@ -274,22 +250,14 @@ class ToHtml(Tokenizer):
|
|
274
250
|
elif tType == self.T_SKIP:
|
275
251
|
lines.append(f"<p class='skip'{hStyle}> </p>\n")
|
276
252
|
|
277
|
-
elif tType == self.T_TEXT:
|
278
|
-
tTemp = tText
|
279
|
-
if pStyle is None:
|
280
|
-
pStyle = hStyle
|
281
|
-
for pos, fmt in reversed(tFormat):
|
282
|
-
tTemp = f"{tTemp[:pos]}{htmlTags[fmt]}{tTemp[pos:]}"
|
283
|
-
para.append(stripEscape(tTemp.rstrip()))
|
284
|
-
|
285
253
|
elif tType == self.T_SYNOPSIS and self._doSynopsis:
|
286
|
-
lines.append(self._formatSynopsis(tText, True))
|
254
|
+
lines.append(self._formatSynopsis(self._formatText(tText, tFormat), True))
|
287
255
|
|
288
256
|
elif tType == self.T_SHORT and self._doSynopsis:
|
289
|
-
lines.append(self._formatSynopsis(tText, False))
|
257
|
+
lines.append(self._formatSynopsis(self._formatText(tText, tFormat), False))
|
290
258
|
|
291
259
|
elif tType == self.T_COMMENT and self._doComments:
|
292
|
-
lines.append(self._formatComments(tText))
|
260
|
+
lines.append(self._formatComments(self._formatText(tText, tFormat)))
|
293
261
|
|
294
262
|
elif tType == self.T_KEYWORD and self._doKeywords:
|
295
263
|
tag, text = self._formatKeywords(tText)
|
@@ -302,6 +270,26 @@ class ToHtml(Tokenizer):
|
|
302
270
|
|
303
271
|
return
|
304
272
|
|
273
|
+
def appendFootnotes(self) -> None:
|
274
|
+
"""Append the footnotes in the buffer."""
|
275
|
+
if self._usedNotes:
|
276
|
+
footnotes = self._localLookup("Footnotes")
|
277
|
+
|
278
|
+
lines = []
|
279
|
+
lines.append(f"<h3>{footnotes}</h3>\n")
|
280
|
+
lines.append("<ol>\n")
|
281
|
+
for key, index in self._usedNotes.items():
|
282
|
+
if content := self._footnotes.get(key):
|
283
|
+
text = self._formatText(*content)
|
284
|
+
lines.append(f"<li id='footnote_{index}'><p>{text}</p></li>\n")
|
285
|
+
lines.append("</ol>\n")
|
286
|
+
|
287
|
+
result = "".join(lines)
|
288
|
+
self._result += result
|
289
|
+
self._fullHTML.append(result)
|
290
|
+
|
291
|
+
return
|
292
|
+
|
305
293
|
def saveHtml5(self, path: str | Path) -> None:
|
306
294
|
"""Save the data to an HTML file."""
|
307
295
|
with open(path, mode="w", encoding="utf-8") as fObj:
|
@@ -367,8 +355,16 @@ class ToHtml(Tokenizer):
|
|
367
355
|
mScale = self._lineHeight/1.15
|
368
356
|
|
369
357
|
styles = []
|
370
|
-
|
371
|
-
|
358
|
+
font = self._textFont
|
359
|
+
styles.append((
|
360
|
+
"body {{"
|
361
|
+
"font-family: '{0:s}'; font-size: {1:d}pt; "
|
362
|
+
"font-weight: {2:d}; font-style: {3:s};"
|
363
|
+
"}}"
|
364
|
+
).format(
|
365
|
+
font.family(), font.pointSize(),
|
366
|
+
FONT_WEIGHTS.get(font.weight(), 400),
|
367
|
+
FONT_STYLE.get(font.style(), "normal"),
|
372
368
|
))
|
373
369
|
styles.append((
|
374
370
|
"p {{"
|
@@ -442,10 +438,12 @@ class ToHtml(Tokenizer):
|
|
442
438
|
|
443
439
|
styles.append("a {color: rgb(66, 113, 174);}")
|
444
440
|
styles.append("mark {background: rgb(255, 255, 166);}")
|
445
|
-
styles.append(".
|
441
|
+
styles.append(".keyword {color: rgb(245, 135, 31); font-weight: bold;}")
|
446
442
|
styles.append(".break {text-align: left;}")
|
447
443
|
styles.append(".synopsis {font-style: italic;}")
|
448
444
|
styles.append(".comment {font-style: italic; color: rgb(100, 100, 100);}")
|
445
|
+
styles.append(".dialog {color: rgb(66, 113, 174);}")
|
446
|
+
styles.append(".altdialog {color: rgb(129, 55, 9);}")
|
449
447
|
|
450
448
|
return styles
|
451
449
|
|
@@ -453,24 +451,63 @@ class ToHtml(Tokenizer):
|
|
453
451
|
# Internal Functions
|
454
452
|
##
|
455
453
|
|
454
|
+
def _formatText(self, text: str, tFmt: T_Formats) -> str:
|
455
|
+
"""Apply formatting tags to text."""
|
456
|
+
temp = text
|
457
|
+
|
458
|
+
# Build a list of all html tags that need to be inserted in the text.
|
459
|
+
# This is done in the forward direction, and a tag is only opened if it
|
460
|
+
# isn't already open, and only closed if it has previously been opened.
|
461
|
+
tags: list[tuple[int, str]] = []
|
462
|
+
state = dict.fromkeys(HTML_OPENER, False)
|
463
|
+
for pos, fmt, data in tFmt:
|
464
|
+
if m := HTML_OPENER.get(fmt):
|
465
|
+
if not state.get(fmt, True):
|
466
|
+
tags.append((pos, m[1]))
|
467
|
+
state[fmt] = True
|
468
|
+
elif m := HTML_CLOSER.get(fmt):
|
469
|
+
if state.get(m[0], False):
|
470
|
+
tags.append((pos, m[1]))
|
471
|
+
state[m[0]] = False
|
472
|
+
elif fmt == self.FMT_FNOTE:
|
473
|
+
if data in self._footnotes:
|
474
|
+
index = len(self._usedNotes) + 1
|
475
|
+
self._usedNotes[data] = index
|
476
|
+
tags.append((pos, f"<sup><a href='#footnote_{index}'>{index}</a></sup>"))
|
477
|
+
else:
|
478
|
+
tags.append((pos, "<sup>ERR</sup>"))
|
479
|
+
|
480
|
+
# Check all format types and close any tag that is still open. This
|
481
|
+
# ensures that unclosed tags don't spill over to the next paragraph.
|
482
|
+
end = len(text)
|
483
|
+
for opener, active in state.items():
|
484
|
+
if active:
|
485
|
+
closer = HTML_OPENER.get(opener, HTML_NONE)[0]
|
486
|
+
tags.append((end, HTML_CLOSER.get(closer, HTML_NONE)[1]))
|
487
|
+
|
488
|
+
# Insert all tags at their correct position, starting from the back.
|
489
|
+
# The reverse order ensures that the positions are not shifted while we
|
490
|
+
# insert tags.
|
491
|
+
for pos, tag in reversed(tags):
|
492
|
+
temp = f"{temp[:pos]}{tag}{temp[pos:]}"
|
493
|
+
|
494
|
+
# Replace all line breaks with proper HTML break tags
|
495
|
+
temp = temp.replace("\n", "<br>")
|
496
|
+
|
497
|
+
return stripEscape(temp)
|
498
|
+
|
456
499
|
def _formatSynopsis(self, text: str, synopsis: bool) -> str:
|
457
500
|
"""Apply HTML formatting to synopsis."""
|
458
501
|
if synopsis:
|
459
502
|
sSynop = self._localLookup("Synopsis")
|
460
503
|
else:
|
461
504
|
sSynop = self._localLookup("Short Description")
|
462
|
-
|
463
|
-
return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {text}</p>\n"
|
464
|
-
else:
|
465
|
-
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
|
505
|
+
return f"<p class='synopsis'><strong>{sSynop}:</strong> {text}</p>\n"
|
466
506
|
|
467
507
|
def _formatComments(self, text: str) -> str:
|
468
508
|
"""Apply HTML formatting to comments."""
|
469
|
-
|
470
|
-
|
471
|
-
else:
|
472
|
-
sComm = self._localLookup("Comment")
|
473
|
-
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
|
509
|
+
sComm = self._localLookup("Comment")
|
510
|
+
return f"<p class='comment'><strong>{sComm}:</strong> {text}</p>\n"
|
474
511
|
|
475
512
|
def _formatKeywords(self, text: str) -> tuple[str, str]:
|
476
513
|
"""Apply HTML formatting to keywords."""
|
@@ -478,19 +515,16 @@ class ToHtml(Tokenizer):
|
|
478
515
|
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
479
516
|
return "", ""
|
480
517
|
|
481
|
-
result = f"<span class='
|
518
|
+
result = f"<span class='keyword'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
|
482
519
|
if len(bits) > 1:
|
483
520
|
if bits[0] == nwKeyWords.TAG_KEY:
|
484
521
|
one, two = self._project.index.parseValue(bits[1])
|
485
|
-
result += f"<a name='tag_{one}'>{one}</a>"
|
522
|
+
result += f"<a class='tag' name='tag_{one}'>{one}</a>"
|
486
523
|
if two:
|
487
524
|
result += f" | <span class='optional'>{two}</a>"
|
488
525
|
else:
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
result += ", ".join(f"<a href='#tag_{t}'>{t}</a>" for t in bits[1:])
|
526
|
+
result += ", ".join(
|
527
|
+
f"<a class='tag' href='#tag_{t}'>{t}</a>" for t in bits[1:]
|
528
|
+
)
|
493
529
|
|
494
530
|
return bits[0][1:], result
|
495
|
-
|
496
|
-
# END Class ToHtml
|