Merge 2.7.2 into main
This commit is contained in:
@@ -29,7 +29,15 @@ from enum import Flag, IntEnum
|
||||
|
||||
from PyQt6.QtGui import QColor
|
||||
|
||||
ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
|
||||
ESCAPES = {
|
||||
r"\*": "*",
|
||||
r"\~": "~",
|
||||
r"\=": "=",
|
||||
r"\_": "_",
|
||||
r"\[": "[",
|
||||
r"\]": "]",
|
||||
r"\ ": "",
|
||||
}
|
||||
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ from novelwriter import __version__
|
||||
from novelwriter.common import firstFloat, xmlElement, xmlSubElem
|
||||
from novelwriter.constants import nwHeadFmt, nwStyles
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
|
||||
from novelwriter.types import QtHexRgb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -296,7 +296,7 @@ class ToDocX(Tokenizer):
|
||||
elif tType == BlockTyp.SKIP:
|
||||
self._processFragments(par, S_NORM, "")
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
elif tType in COMMENT_BLOCKS:
|
||||
self._processFragments(par, S_META, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
|
||||
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING
|
||||
from novelwriter.common import formatTimeStamp
|
||||
from novelwriter.constants import nwHtmlUnicode, nwStyles
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
|
||||
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -159,34 +159,33 @@ class ToHtml(Tokenizer):
|
||||
# If we don't have formatting, we can do a plain replace
|
||||
tText = tText.replace("<", "<").replace(">", ">")
|
||||
|
||||
# Styles
|
||||
# Inline Styles
|
||||
aStyle = []
|
||||
if self._cssStyles:
|
||||
if tStyle & BlockFmt.LEFT:
|
||||
aStyle.append("text-align: left;")
|
||||
elif tStyle & BlockFmt.RIGHT:
|
||||
aStyle.append("text-align: right;")
|
||||
elif tStyle & BlockFmt.CENTRE:
|
||||
aStyle.append("text-align: center;")
|
||||
elif tStyle & BlockFmt.JUSTIFY:
|
||||
aStyle.append("text-align: justify;")
|
||||
if tStyle & BlockFmt.LEFT:
|
||||
aStyle.append("text-align: left;")
|
||||
elif tStyle & BlockFmt.RIGHT:
|
||||
aStyle.append("text-align: right;")
|
||||
elif tStyle & BlockFmt.CENTRE:
|
||||
aStyle.append("text-align: center;")
|
||||
elif tStyle & BlockFmt.JUSTIFY:
|
||||
aStyle.append("text-align: justify;")
|
||||
|
||||
if tStyle & BlockFmt.PBB:
|
||||
aStyle.append("page-break-before: always;")
|
||||
if tStyle & BlockFmt.PBA:
|
||||
aStyle.append("page-break-after: always;")
|
||||
if tStyle & BlockFmt.PBB:
|
||||
aStyle.append("page-break-before: always;")
|
||||
if tStyle & BlockFmt.PBA:
|
||||
aStyle.append("page-break-after: always;")
|
||||
|
||||
if tStyle & BlockFmt.Z_BTM:
|
||||
aStyle.append("margin-bottom: 0;")
|
||||
if tStyle & BlockFmt.Z_TOP:
|
||||
aStyle.append("margin-top: 0;")
|
||||
if tStyle & BlockFmt.Z_BTM:
|
||||
aStyle.append("margin-bottom: 0;")
|
||||
if tStyle & BlockFmt.Z_TOP:
|
||||
aStyle.append("margin-top: 0;")
|
||||
|
||||
if tStyle & BlockFmt.IND_L:
|
||||
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
|
||||
if tStyle & BlockFmt.IND_R:
|
||||
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
|
||||
if tStyle & BlockFmt.IND_T:
|
||||
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
|
||||
if tStyle & BlockFmt.IND_L:
|
||||
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
|
||||
if tStyle & BlockFmt.IND_R:
|
||||
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
|
||||
if tStyle & BlockFmt.IND_T:
|
||||
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
|
||||
|
||||
if aStyle:
|
||||
stVals = " ".join(aStyle)
|
||||
@@ -229,7 +228,7 @@ class ToHtml(Tokenizer):
|
||||
elif tType == BlockTyp.SKIP:
|
||||
lines.append(f"<p{hStyle}> </p>\n")
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
elif tType in COMMENT_BLOCKS:
|
||||
lines.append(f"<p class='comment'{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
@@ -288,26 +287,25 @@ class ToHtml(Tokenizer):
|
||||
json.dump(data, fObj, indent=2)
|
||||
|
||||
else:
|
||||
html = []
|
||||
html.append("<!DOCTYPE html>")
|
||||
html.append("<html>")
|
||||
html.append("<head>")
|
||||
html.append(f"<title>{self._project.data.name:s}</title>")
|
||||
html.append("<meta charset='utf-8'>")
|
||||
if self._cssStyles:
|
||||
html.append("<meta name='viewport' content='width=device-width, initial-scale=1'>")
|
||||
html.append("<style>")
|
||||
html.extend(self.getStyleSheet())
|
||||
html.append("</style>")
|
||||
html.append("</head>")
|
||||
html.append("<body>")
|
||||
html.append(("".join(self._pages)).replace("\t", "	").rstrip())
|
||||
html.append("</body>")
|
||||
html.append("</html>\n")
|
||||
|
||||
with open(path, mode="w", encoding="utf-8") as fObj:
|
||||
fObj.write((
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<title>{title:s}</title>\n"
|
||||
"<style>\n"
|
||||
"{style:s}\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"{body:s}\n"
|
||||
"</body>\n"
|
||||
"</html>\n"
|
||||
).format(
|
||||
title=self._project.data.name,
|
||||
style="\n".join(self.getStyleSheet()),
|
||||
body=("".join(self._pages)).replace("\t", "	").rstrip(),
|
||||
))
|
||||
fObj.write("\n".join(html))
|
||||
|
||||
logger.info("Wrote file: %s", path)
|
||||
|
||||
@@ -410,7 +408,11 @@ class ToHtml(Tokenizer):
|
||||
# isn't already open, and only closed if it has previously been opened.
|
||||
tags: list[tuple[int, str]] = []
|
||||
state = dict.fromkeys(HTML_OPENER, False)
|
||||
plain = not self._cssStyles
|
||||
for pos, fmt, data in tFmt:
|
||||
if plain and fmt in (TextFmt.COL_B, TextFmt.COL_E):
|
||||
# We ignore colour tags if CSS is off
|
||||
continue
|
||||
if m := HTML_OPENER.get(fmt):
|
||||
if not state.get(fmt, True):
|
||||
if fmt == TextFmt.COL_B and (color := self._classes.get(data)):
|
||||
|
||||
@@ -71,11 +71,23 @@ COMMENT_STYLE = {
|
||||
nwComment.COMMENT: ComStyle(),
|
||||
nwComment.STORY: ComStyle("Story Structure", "modifier", "note"),
|
||||
}
|
||||
HEADINGS = [
|
||||
COMMENT_TYPE = {
|
||||
nwComment.PLAIN: BlockTyp.COMMENT,
|
||||
nwComment.IGNORE: BlockTyp.COMMENT,
|
||||
nwComment.SYNOPSIS: BlockTyp.SUMMARY,
|
||||
nwComment.SHORT: BlockTyp.SUMMARY,
|
||||
nwComment.NOTE: BlockTyp.NOTE,
|
||||
nwComment.FOOTNOTE: BlockTyp.COMMENT,
|
||||
nwComment.COMMENT: BlockTyp.COMMENT,
|
||||
nwComment.STORY: BlockTyp.NOTE,
|
||||
}
|
||||
HEADING_BLOCKS = [
|
||||
BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1,
|
||||
BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4,
|
||||
]
|
||||
SKIP_INDENT = [*HEADINGS, BlockTyp.SEP, BlockTyp.SKIP]
|
||||
COMMENT_BLOCKS = (BlockTyp.COMMENT, BlockTyp.SUMMARY, BlockTyp.NOTE)
|
||||
META_BLOCKS = (BlockTyp.COMMENT, BlockTyp.SUMMARY, BlockTyp.NOTE, BlockTyp.KEYWORD)
|
||||
SKIP_INDENT = [*HEADING_BLOCKS, BlockTyp.SEP, BlockTyp.SKIP]
|
||||
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
|
||||
|
||||
|
||||
@@ -189,6 +201,7 @@ class Tokenizer(ABC):
|
||||
(REGEX_PATTERNS.markdownItalic, [0, TextFmt.I_B, 0, TextFmt.I_E]),
|
||||
(REGEX_PATTERNS.markdownBold, [0, TextFmt.B_B, 0, TextFmt.B_E]),
|
||||
(REGEX_PATTERNS.markdownStrike, [0, TextFmt.D_B, 0, TextFmt.D_E]),
|
||||
(REGEX_PATTERNS.markdownMark, [0, TextFmt.M_B, 0, TextFmt.M_E]),
|
||||
]
|
||||
|
||||
self._shortCodeFmt = {
|
||||
@@ -618,7 +631,7 @@ class Tokenizer(ABC):
|
||||
bStyle = COMMENT_STYLE[cStyle]
|
||||
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
|
||||
tBlocks.append((
|
||||
BlockTyp.COMMENT, "", tLine, tFmt, tStyle
|
||||
COMMENT_TYPE[cStyle], "", tLine, tFmt, tStyle
|
||||
))
|
||||
|
||||
elif cStyle == nwComment.FOOTNOTE:
|
||||
@@ -862,12 +875,12 @@ class Tokenizer(ABC):
|
||||
# We don't need to keep the empty lines after this pass
|
||||
pass
|
||||
|
||||
elif cBlock[0] == BlockTyp.KEYWORD:
|
||||
# Adjust margins for lines in a list of keyword lines
|
||||
elif cBlock[0] in (BlockTyp.KEYWORD, BlockTyp.NOTE):
|
||||
# Adjust margins for lines in repeated meta blocks
|
||||
aStyle = cBlock[4]
|
||||
if pBlock[0] == BlockTyp.KEYWORD:
|
||||
if pBlock[0] == cBlock[0]:
|
||||
aStyle |= BlockFmt.Z_TOP
|
||||
if nBlock[0] == BlockTyp.KEYWORD:
|
||||
if nBlock[0] == cBlock[0]:
|
||||
aStyle |= BlockFmt.Z_BTM
|
||||
sBlocks.append((
|
||||
cBlock[0], cBlock[1], cBlock[2], cBlock[3], aStyle
|
||||
@@ -991,7 +1004,7 @@ class Tokenizer(ABC):
|
||||
allWordChars += nPWChars
|
||||
textWordChars += nPWChars
|
||||
|
||||
elif tType in HEADINGS:
|
||||
elif tType in HEADING_BLOCKS:
|
||||
titleCount += 1
|
||||
allWords += nWords
|
||||
titleWords += nWords
|
||||
@@ -1005,7 +1018,7 @@ class Tokenizer(ABC):
|
||||
allChars += nChars
|
||||
allWordChars += nWChars
|
||||
|
||||
elif tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD):
|
||||
elif tType in META_BLOCKS:
|
||||
words = tText.split()
|
||||
allWords += len(words)
|
||||
allChars += len(tText)
|
||||
|
||||
@@ -29,7 +29,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
@@ -144,7 +144,7 @@ class ToMarkdown(Tokenizer):
|
||||
elif tType == BlockTyp.SKIP:
|
||||
lines.append(f"{cSkip}\n\n")
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
elif tType in COMMENT_BLOCKS:
|
||||
lines.append(f"{self._formatText(tText, tFormat, mTags)}\n\n")
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
|
||||
@@ -40,7 +40,7 @@ from novelwriter import __version__
|
||||
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
|
||||
from novelwriter.constants import nwHeadFmt, nwStyles
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
|
||||
from novelwriter.types import FONT_STYLE, QtHexRgb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -389,7 +389,7 @@ class ToOdt(Tokenizer):
|
||||
elif tType == BlockTyp.SKIP:
|
||||
self._addTextPar(xText, S_TEXT, oStyle, "")
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
elif tType in COMMENT_BLOCKS:
|
||||
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
|
||||
@@ -37,7 +37,7 @@ from PyQt6.QtPrintSupport import QPrinter
|
||||
from novelwriter import __version__
|
||||
from novelwriter.constants import nwStyles, nwUnicode
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
|
||||
from novelwriter.formats.tokenizer import HEADING_BLOCKS, META_BLOCKS, Tokenizer
|
||||
from novelwriter.types import (
|
||||
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
|
||||
QtKeepAnchor, QtMoveAnchor, QtPageBreakAfter, QtPageBreakAuto,
|
||||
@@ -214,7 +214,7 @@ class ToQTextDocument(Tokenizer):
|
||||
for tType, tMeta, tText, tFormat, tStyle in self._blocks:
|
||||
|
||||
bFmt = QTextBlockFormat(self._blockFmt)
|
||||
if tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD):
|
||||
if tType in META_BLOCKS:
|
||||
bFmt.setTopMargin(self._mMeta[0])
|
||||
bFmt.setBottomMargin(self._mMeta[1])
|
||||
elif tType == BlockTyp.SEP:
|
||||
@@ -248,17 +248,21 @@ class ToQTextDocument(Tokenizer):
|
||||
if tStyle & BlockFmt.IND_T:
|
||||
bFmt.setTextIndent(self._tIndent)
|
||||
|
||||
if tType in (BlockTyp.TEXT, BlockTyp.COMMENT, BlockTyp.KEYWORD):
|
||||
if tType == BlockTyp.TEXT:
|
||||
newBlock(cursor, bFmt)
|
||||
self._insertFragments(tText, tFormat, cursor, self._charFmt)
|
||||
|
||||
elif tType in HEADINGS:
|
||||
elif tType in HEADING_BLOCKS:
|
||||
bFmt, cFmt = self._genHeadStyle(tType, tMeta, bFmt)
|
||||
for tPart in tText.split("\n"):
|
||||
newBlock(cursor, bFmt)
|
||||
cursor.insertText(tPart, cFmt)
|
||||
bFmt.setPageBreakPolicy(QtPageBreakAuto)
|
||||
|
||||
elif tType in META_BLOCKS:
|
||||
newBlock(cursor, bFmt)
|
||||
self._insertFragments(tText, tFormat, cursor, self._charFmt)
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
newBlock(cursor, bFmt)
|
||||
cursor.insertText(tText, self._charFmt)
|
||||
|
||||
Reference in New Issue
Block a user