Process tags and references in Tokenizer
This commit is contained in:
@@ -86,8 +86,12 @@ class TextFmt(IntEnum):
|
||||
SUB_E = 14 # End subscript
|
||||
COL_B = 15 # Begin colour
|
||||
COL_E = 16 # End colour
|
||||
FNOTE = 17 # Footnote marker
|
||||
STRIP = 18 # Strip the format code
|
||||
ANM_B = 17 # Begin anchor name
|
||||
ANM_E = 18 # End anchor name
|
||||
HRF_B = 19 # Begin href link
|
||||
HRF_E = 20 # End href link
|
||||
FNOTE = 21 # Footnote marker
|
||||
STRIP = 22 # Strip the format code
|
||||
|
||||
|
||||
class BlockTyp(IntEnum):
|
||||
|
||||
@@ -38,7 +38,7 @@ from PyQt5.QtGui import QColor
|
||||
|
||||
from novelwriter import __version__
|
||||
from novelwriter.common import firstFloat, xmlSubElem
|
||||
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles
|
||||
from novelwriter.constants import nwHeadFmt, nwStyles
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
@@ -119,12 +119,6 @@ S_META = "MetaText"
|
||||
S_HEAD = "Header"
|
||||
S_FNOTE = "FootnoteText"
|
||||
|
||||
# Colours
|
||||
COL_DIALOG_M = "2a6099"
|
||||
COL_DIALOG_A = "813709"
|
||||
COL_META_TXT = "813709"
|
||||
COL_MARK_TXT = "ffffa6"
|
||||
|
||||
|
||||
class DocXXmlFile(NamedTuple):
|
||||
|
||||
@@ -292,8 +286,7 @@ class ToDocX(Tokenizer):
|
||||
self._processFragments(par, S_META, tText, tFormat)
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
tTemp, tFmt = self._formatKeywords(tText)
|
||||
self._processFragments(par, S_META, tTemp, tFmt)
|
||||
self._processFragments(par, S_META, tText, tFormat)
|
||||
|
||||
return
|
||||
|
||||
@@ -369,22 +362,6 @@ class ToDocX(Tokenizer):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
|
||||
"""Apply formatting to keywords."""
|
||||
valid, bits, _ = self._project.index.scanThis("@"+text)
|
||||
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
||||
return "", []
|
||||
|
||||
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
||||
rFmt: T_Formats = [(0, TextFmt.B_B, ""), (len(rTxt) - 1, TextFmt.B_E, "")]
|
||||
if len(bits) > 1:
|
||||
if bits[0] == nwKeyWords.TAG_KEY:
|
||||
rTxt += bits[1]
|
||||
else:
|
||||
rTxt += ", ".join(bits[1:])
|
||||
|
||||
return rTxt, rFmt
|
||||
|
||||
def _processFragments(
|
||||
self, par: DocXParagraph, pStyle: str, text: str, tFmt: T_Formats | None = None
|
||||
) -> None:
|
||||
@@ -465,7 +442,7 @@ class ToDocX(Tokenizer):
|
||||
xmlSubElem(rPr, _wTag("u"), attrib={_wTag("val"): "single"})
|
||||
if fmt & X_MRK:
|
||||
xmlSubElem(rPr, _wTag("shd"), attrib={
|
||||
_wTag("fill"): COL_MARK_TXT, _wTag("val"): "clear",
|
||||
_wTag("fill"): _docXCol(self._theme.highlight), _wTag("val"): "clear",
|
||||
})
|
||||
if fmt & X_DEL:
|
||||
xmlSubElem(rPr, _wTag("strike"))
|
||||
|
||||
@@ -30,7 +30,7 @@ from pathlib import Path
|
||||
from time import time
|
||||
|
||||
from novelwriter.common import formatTimeStamp
|
||||
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode, nwKeyWords, nwLabels
|
||||
from novelwriter.constants import nwHeadFmt, nwHtmlUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
@@ -48,6 +48,8 @@ HTML_OPENER: dict[int, tuple[int, str]] = {
|
||||
TextFmt.SUP_B: (TextFmt.SUP_E, "<sup>"),
|
||||
TextFmt.SUB_B: (TextFmt.SUB_E, "<sub>"),
|
||||
TextFmt.COL_B: (TextFmt.COL_E, "<span style='color: {0}'>"),
|
||||
TextFmt.ANM_B: (TextFmt.ANM_E, "<a name='{0}'>"),
|
||||
TextFmt.HRF_B: (TextFmt.HRF_E, "<a href='{0}'>"),
|
||||
}
|
||||
|
||||
# Each closer tag, with the id of its corresponding opener and tag format
|
||||
@@ -60,6 +62,8 @@ HTML_CLOSER: dict[int, tuple[int, str]] = {
|
||||
TextFmt.SUP_E: (TextFmt.SUP_B, "</sup>"),
|
||||
TextFmt.SUB_E: (TextFmt.SUB_B, "</sub>"),
|
||||
TextFmt.COL_E: (TextFmt.COL_B, "</span>"),
|
||||
TextFmt.ANM_E: (TextFmt.ANM_B, "</a>"),
|
||||
TextFmt.HRF_E: (TextFmt.HRF_B, "</a>"),
|
||||
}
|
||||
|
||||
# Empty HTML tag record
|
||||
@@ -155,21 +159,21 @@ class ToHtml(Tokenizer):
|
||||
lines = []
|
||||
tHandle = self._handle
|
||||
|
||||
for tType, nHead, tText, tFormat, tStyle in self._blocks:
|
||||
for tType, nHead, tText, tFmt, tStyle in self._blocks:
|
||||
|
||||
# Replace < and > with HTML entities
|
||||
if tFormat:
|
||||
if tFmt:
|
||||
# If we have formatting, we must recompute the locations
|
||||
cText = []
|
||||
i = 0
|
||||
for c in tText:
|
||||
if c == "<":
|
||||
cText.append("<")
|
||||
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
|
||||
tFmt = [(p + 3 if p > i else p, f, k) for p, f, k in tFmt]
|
||||
i += 4
|
||||
elif c == ">":
|
||||
cText.append(">")
|
||||
tFormat = [(p + 3 if p > i else p, f, k) for p, f, k in tFormat]
|
||||
tFmt = [(p + 3 if p > i else p, f, k) for p, f, k in tFmt]
|
||||
i += 4
|
||||
else:
|
||||
cText.append(c)
|
||||
@@ -221,7 +225,7 @@ class ToHtml(Tokenizer):
|
||||
|
||||
# Process Text Type
|
||||
if tType == BlockTyp.TEXT:
|
||||
lines.append(f"<p{hStyle}>{self._formatText(tText, tFormat)}</p>\n")
|
||||
lines.append(f"<p{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
|
||||
|
||||
elif tType == BlockTyp.TITLE:
|
||||
tHead = tText.replace(nwHeadFmt.BR, "<br>")
|
||||
@@ -250,13 +254,10 @@ class ToHtml(Tokenizer):
|
||||
lines.append(f"<p class='skip'{hStyle}> </p>\n")
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
lines.append(f"<p class='comment'>{self._formatText(tText, tFormat)}</p>\n")
|
||||
lines.append(f"<p class='comment'{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
tag, text = self._formatKeywords(tText)
|
||||
kClass = f" class='meta meta-{tag}'" if tag else ""
|
||||
tTemp = f"<p{kClass}{hStyle}>{text}</p>\n"
|
||||
lines.append(tTemp)
|
||||
lines.append(f"<p class='meta'{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
|
||||
|
||||
self._result = "".join(lines)
|
||||
self._fullHTML.append(self._result)
|
||||
@@ -431,9 +432,8 @@ class ToHtml(Tokenizer):
|
||||
mScale, mScale
|
||||
))
|
||||
|
||||
styles.append("a {color: rgb(66, 113, 174);}")
|
||||
styles.append("mark {background: rgb(255, 255, 166);}")
|
||||
styles.append(".keyword {color: rgb(245, 135, 31); font-weight: bold;}")
|
||||
styles.append("a {{color: {0:s};}}".format(self._theme.head.name(QtHexRgb)))
|
||||
styles.append("mark {{background: {0:s};}}".format(self._theme.highlight.name(QtHexRgb)))
|
||||
|
||||
return styles
|
||||
|
||||
@@ -455,6 +455,8 @@ class ToHtml(Tokenizer):
|
||||
if not state.get(fmt, True):
|
||||
if fmt == TextFmt.COL_B and (color := self._classes.get(data)):
|
||||
tags.append((pos, m[1].format(color.name(QtHexRgb))))
|
||||
elif fmt in (TextFmt.ANM_B, TextFmt.HRF_B):
|
||||
tags.append((pos, m[1].format(data or "#")))
|
||||
else:
|
||||
tags.append((pos, m[1]))
|
||||
state[fmt] = True
|
||||
@@ -488,23 +490,3 @@ class ToHtml(Tokenizer):
|
||||
temp = temp.replace("\n", "<br>")
|
||||
|
||||
return stripEscape(temp)
|
||||
|
||||
def _formatKeywords(self, text: str) -> tuple[str, str]:
|
||||
"""Apply HTML formatting to keywords."""
|
||||
valid, bits, _ = self._project.index.scanThis("@"+text)
|
||||
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
||||
return "", ""
|
||||
|
||||
result = f"<span class='keyword'>{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:</span> "
|
||||
if len(bits) > 1:
|
||||
if bits[0] == nwKeyWords.TAG_KEY:
|
||||
one, two = self._project.index.parseValue(bits[1])
|
||||
result += f"<a class='tag' name='tag_{one}'>{one}</a>"
|
||||
if two:
|
||||
result += f" | <span class='optional'>{two}</a>"
|
||||
else:
|
||||
result += ", ".join(
|
||||
f"<a class='tag' href='#tag_{t}'>{t}</a>" for t in bits[1:]
|
||||
)
|
||||
|
||||
return bits[0][1:], result
|
||||
|
||||
@@ -127,10 +127,11 @@ class Tokenizer(ABC):
|
||||
self._doSynopsis = False # Also process synopsis comments
|
||||
self._doComments = False # Also process comments
|
||||
self._doKeywords = False # Also process keywords like tags and references
|
||||
self._skipKeywords = set() # Keywords to ignore
|
||||
self._keepBreaks = True # Keep line breaks in paragraphs
|
||||
self._defaultAlign = "left" # The default text alignment
|
||||
|
||||
self._skipKeywords: set[str] = set() # Keywords to ignore
|
||||
|
||||
# Other Setting
|
||||
self._theme = TextDocumentTheme()
|
||||
self._classes: dict[str, QColor] = {}
|
||||
@@ -464,6 +465,9 @@ class Tokenizer(ABC):
|
||||
self._classes["comment"] = self._theme.comment
|
||||
self._classes["dialog"] = self._theme.dialog
|
||||
self._classes["altdialog"] = self._theme.altdialog
|
||||
self._classes["tag"] = self._theme.tag
|
||||
self._classes["keyword"] = self._theme.keyword
|
||||
self._classes["optional"] = self._theme.optional
|
||||
return
|
||||
|
||||
def addRootHeading(self, tHandle: str) -> None:
|
||||
@@ -614,7 +618,9 @@ class Tokenizer(ABC):
|
||||
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT, nwComment.PLAIN):
|
||||
bStyle = COMMENT_STYLE[cStyle]
|
||||
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
|
||||
blocks.append((BlockTyp.COMMENT, nHead, tLine, tFmt, sAlign))
|
||||
blocks.append((
|
||||
BlockTyp.COMMENT, nHead, tLine, tFmt, sAlign
|
||||
))
|
||||
if self._keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
|
||||
@@ -633,31 +639,14 @@ class Tokenizer(ABC):
|
||||
if not self._doKeywords:
|
||||
continue
|
||||
|
||||
valid, bits, _ = self._project.index.scanThis(aLine)
|
||||
if (
|
||||
valid and bits and bits[0] in nwLabels.KEY_NAME
|
||||
and bits[0] not in self._skipKeywords
|
||||
):
|
||||
tLine, tFmt = self._formatMeta(aLine)
|
||||
if tLine:
|
||||
blocks.append((
|
||||
BlockTyp.KEYWORD, nHead, aLine[1:].strip(), [], sAlign
|
||||
BlockTyp.KEYWORD, nHead, tLine, tFmt, sAlign
|
||||
))
|
||||
if self._keepRaw:
|
||||
tmpMarkdown.append(f"{aLine}\n")
|
||||
|
||||
# valid, bits, _ = self._project.index.scanThis("@"+text)
|
||||
# if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
||||
# return "", []
|
||||
|
||||
# rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
||||
# rFmt: T_Formats = [(0, TextFmt.B_B, ""), (len(rTxt) - 1, TextFmt.B_E, "")]
|
||||
# if len(bits) > 1:
|
||||
# if bits[0] == nwKeyWords.TAG_KEY:
|
||||
# rTxt += bits[1]
|
||||
# else:
|
||||
# rTxt += ", ".join(bits[1:])
|
||||
|
||||
# return rTxt, rFmt
|
||||
|
||||
elif aLine.startswith(("# ", "#! ")):
|
||||
# Title or Partition Headings
|
||||
# ===========================
|
||||
@@ -1033,22 +1022,12 @@ class Tokenizer(ABC):
|
||||
allChars += nChars
|
||||
allWordChars += nWChars
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
elif tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD):
|
||||
words = tText.split()
|
||||
allWords += len(words)
|
||||
allChars += len(tText)
|
||||
allWordChars += len("".join(words))
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
valid, bits, _ = self._project.index.scanThis("@"+tText)
|
||||
if valid and bits:
|
||||
key = self._localLookup(nwLabels.KEY_NAME[bits[0]])
|
||||
text = "{0}: {1}".format(key, ", ".join(bits[1:]))
|
||||
words = text.split()
|
||||
allWords += len(words)
|
||||
allChars += len(text)
|
||||
allWordChars += len("".join(words))
|
||||
|
||||
self._counts["titleCount"] = titleCount
|
||||
self._counts["paragraphCount"] = paragraphCount
|
||||
|
||||
@@ -1112,6 +1091,55 @@ class Tokenizer(ABC):
|
||||
rFmt.extend((p + shift, f, d) for p, f, d in tFmt)
|
||||
return tTxt, rFmt
|
||||
|
||||
def _formatMeta(self, text: str) -> tuple[str, T_Formats]:
|
||||
"""Parse a meta line into a """
|
||||
txt = []
|
||||
fmt = []
|
||||
valid, bits, _ = self._project.index.scanThis(text)
|
||||
if valid and bits and bits[0] in nwLabels.KEY_NAME and bits[0] not in self._skipKeywords:
|
||||
pos = 0
|
||||
lbl = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:"
|
||||
end = len(lbl)
|
||||
fmt = [
|
||||
(pos, TextFmt.B_B, ""), (pos, TextFmt.COL_B, "keyword"),
|
||||
(end, TextFmt.B_E, ""), (end, TextFmt.COL_E, ""),
|
||||
]
|
||||
txt = [lbl, " "]
|
||||
pos = end + 1
|
||||
|
||||
if (num := len(bits)) > 1:
|
||||
if bits[0] == nwKeyWords.TAG_KEY:
|
||||
one, two = self._project.index.parseValue(bits[1])
|
||||
end = pos + len(one)
|
||||
fmt.append((pos, TextFmt.COL_B, "tag"))
|
||||
fmt.append((pos, TextFmt.ANM_B, f"tag_{one}".lower()))
|
||||
fmt.append((end, TextFmt.ANM_E, ""))
|
||||
fmt.append((end, TextFmt.COL_E, ""))
|
||||
txt.append(one)
|
||||
pos = end
|
||||
if two:
|
||||
txt.append(" | ")
|
||||
pos += 3
|
||||
end = pos + len(two)
|
||||
fmt.append((pos, TextFmt.COL_B, "optional"))
|
||||
fmt.append((end, TextFmt.COL_E, ""))
|
||||
txt.append(two)
|
||||
pos = end
|
||||
else:
|
||||
for n, bit in enumerate(bits[1:], 2):
|
||||
end = pos + len(bit)
|
||||
fmt.append((pos, TextFmt.COL_B, "tag"))
|
||||
fmt.append((pos, TextFmt.HRF_B, f"#tag_{bit}".lower()))
|
||||
fmt.append((end, TextFmt.HRF_E, ""))
|
||||
fmt.append((end, TextFmt.COL_E, ""))
|
||||
txt.append(bit)
|
||||
pos = end
|
||||
if n < num:
|
||||
txt.append(", ")
|
||||
pos += 2
|
||||
|
||||
return "".join(txt), fmt
|
||||
|
||||
def _extractFormats(
|
||||
self, text: str, skip: int = 0, hDialog: bool = False
|
||||
) -> tuple[str, T_Formats]:
|
||||
|
||||
@@ -27,7 +27,7 @@ import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from novelwriter.constants import nwHeadFmt, nwLabels, nwUnicode
|
||||
from novelwriter.constants import nwHeadFmt, nwUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
@@ -154,7 +154,8 @@ class ToMarkdown(Tokenizer):
|
||||
lines.append(f"{self._formatText(tText, tFormat, mTags)}\n\n")
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
lines.append(self._formatKeywords(tText, tStyle))
|
||||
end = " \n" if tStyle & BlockFmt.Z_BTMMRG else "\n\n"
|
||||
lines.append(f"{self._formatText(tText, tFormat, mTags)}{end}")
|
||||
|
||||
self._result = "".join(lines)
|
||||
self._fullMD.append(self._result)
|
||||
@@ -215,19 +216,3 @@ class ToMarkdown(Tokenizer):
|
||||
md = tags.get(fmt, "")
|
||||
temp = f"{temp[:pos]}{md}{temp[pos:]}"
|
||||
return temp
|
||||
|
||||
def _formatKeywords(self, text: str, style: BlockFmt) -> str:
|
||||
"""Apply Markdown formatting to keywords."""
|
||||
valid, bits, _ = self._project.index.scanThis("@"+text)
|
||||
if not valid or not bits:
|
||||
return ""
|
||||
|
||||
result = ""
|
||||
if bits[0] in nwLabels.KEY_NAME:
|
||||
result += f"**{self._localLookup(nwLabels.KEY_NAME[bits[0]])}:** "
|
||||
if len(bits) > 1:
|
||||
result += ", ".join(bits[1:])
|
||||
|
||||
result += " \n" if style & BlockFmt.Z_BTMMRG else "\n\n"
|
||||
|
||||
return result
|
||||
|
||||
@@ -39,11 +39,11 @@ from PyQt5.QtGui import QColor, QFont
|
||||
|
||||
from novelwriter import __version__
|
||||
from novelwriter.common import xmlIndent, xmlSubElem
|
||||
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles
|
||||
from novelwriter.constants import nwHeadFmt, nwStyles
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS
|
||||
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -217,13 +217,6 @@ class ToOdt(Tokenizer):
|
||||
self._mDocLeft = "2.000cm"
|
||||
self._mDocRight = "2.000cm"
|
||||
|
||||
# Colour
|
||||
self._colDialogM = "#2a6099"
|
||||
self._colDialogA = "#813709"
|
||||
self._colMetaTx = "#813709"
|
||||
self._opaMetaTx = "100%"
|
||||
self._markText = "#ffffa6"
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
@@ -484,8 +477,7 @@ class ToOdt(Tokenizer):
|
||||
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
tTemp, tFmt = self._formatKeywords(tText)
|
||||
self._addTextPar(xText, S_META, oStyle, tTemp, tFmt=tFmt)
|
||||
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
|
||||
|
||||
return
|
||||
|
||||
@@ -553,22 +545,6 @@ class ToOdt(Tokenizer):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _formatKeywords(self, text: str) -> tuple[str, T_Formats]:
|
||||
"""Apply formatting to keywords."""
|
||||
valid, bits, _ = self._project.index.scanThis("@"+text)
|
||||
if not valid or not bits or bits[0] not in nwLabels.KEY_NAME:
|
||||
return "", []
|
||||
|
||||
rTxt = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
||||
rFmt: T_Formats = [(0, TextFmt.B_B, ""), (len(rTxt) - 1, TextFmt.B_E, "")]
|
||||
if len(bits) > 1:
|
||||
if bits[0] == nwKeyWords.TAG_KEY:
|
||||
rTxt += bits[1]
|
||||
else:
|
||||
rTxt += ", ".join(bits[1:])
|
||||
|
||||
return rTxt, rFmt
|
||||
|
||||
def _addTextPar(
|
||||
self,
|
||||
xParent: ET.Element,
|
||||
@@ -598,7 +574,6 @@ class ToOdt(Tokenizer):
|
||||
|
||||
parProc = XMLParagraph(xElem)
|
||||
|
||||
pErr = 0
|
||||
xFmt = 0x00
|
||||
tFrag = ""
|
||||
fLast = 0
|
||||
@@ -657,8 +632,6 @@ class ToOdt(Tokenizer):
|
||||
xNode = self._generateFootnote(fData)
|
||||
elif fFmt == TextFmt.STRIP:
|
||||
pass
|
||||
else:
|
||||
pErr += 1
|
||||
|
||||
fLast = fPos
|
||||
|
||||
@@ -671,9 +644,6 @@ class ToOdt(Tokenizer):
|
||||
else:
|
||||
parProc.appendSpan(tFrag, self._textStyle(xFmt, fClass))
|
||||
|
||||
if pErr > 0:
|
||||
self._errData.append("Unknown format tag encountered")
|
||||
|
||||
nErr, errMsg = parProc.checkError()
|
||||
if nErr > 0: # pragma: no cover
|
||||
# This one should only capture bugs
|
||||
@@ -725,17 +695,13 @@ class ToOdt(Tokenizer):
|
||||
style.setUnderlineWidth("auto")
|
||||
style.setUnderlineColor("font-color")
|
||||
if hFmt & X_MRK:
|
||||
style.setBackgroundColor(self._markText)
|
||||
style.setBackgroundColor(self._theme.highlight)
|
||||
if hFmt & X_SUP:
|
||||
style.setTextPosition("super")
|
||||
if hFmt & X_SUB:
|
||||
style.setTextPosition("sub")
|
||||
if hFmt & X_COL and color:
|
||||
style.setColor(color)
|
||||
# if hFmt & X_DLG:
|
||||
# style.setColour(self._colDialogM)
|
||||
# if hFmt & X_DLA:
|
||||
# style.setColour(self._colDialogA)
|
||||
self._autoText[tKey] = style
|
||||
|
||||
return style.name
|
||||
@@ -1265,7 +1231,7 @@ class ODTParagraphStyle:
|
||||
def setColor(self, value: QColor | None) -> None:
|
||||
"""Set text colour."""
|
||||
if isinstance(value, QColor):
|
||||
self._tAttr["color"][1] = value.name(QColor.NameFormat.HexRgb)
|
||||
self._tAttr["color"][1] = value.name(QtHexRgb)
|
||||
self._tAttr["opacity"][1] = f"{int(100.0 * value.alphaF())}%"
|
||||
else:
|
||||
self._tAttr["color"][1] = None
|
||||
@@ -1373,15 +1339,15 @@ class ODTTextStyle:
|
||||
def setColor(self, value: QColor | None) -> None:
|
||||
"""Set text colour."""
|
||||
if isinstance(value, QColor):
|
||||
self._tAttr["color"][1] = value.name(QColor.NameFormat.HexRgb)
|
||||
self._tAttr["color"][1] = value.name(QtHexRgb)
|
||||
else:
|
||||
self._tAttr["color"][1] = None
|
||||
return
|
||||
|
||||
def setBackgroundColor(self, value: str | None) -> None:
|
||||
def setBackgroundColor(self, value: QColor | None) -> None:
|
||||
"""Set text background colour."""
|
||||
if value and len(value) == 7 and value[0] == "#":
|
||||
self._tAttr["background-color"][1] = value
|
||||
if isinstance(value, QColor):
|
||||
self._tAttr["background-color"][1] = value.name(QtHexRgb)
|
||||
else:
|
||||
self._tAttr["background-color"][1] = None
|
||||
return
|
||||
|
||||
@@ -34,7 +34,7 @@ from PyQt5.QtGui import (
|
||||
)
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, nwUnicode
|
||||
from novelwriter.constants import nwHeadFmt, nwStyles, nwUnicode
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
|
||||
from novelwriter.formats.tokenizer import Tokenizer
|
||||
@@ -146,7 +146,7 @@ class ToQTextDocument(Tokenizer):
|
||||
self._mIndent = mPx * 2.0
|
||||
self._tIndent = mPx * self._firstWidth
|
||||
|
||||
# Block Format
|
||||
# Text Formats
|
||||
# ============
|
||||
|
||||
self._blockFmt = QTextBlockFormat()
|
||||
@@ -157,24 +157,9 @@ class ToQTextDocument(Tokenizer):
|
||||
100*self._lineHeight, QTextBlockFormat.LineHeightTypes.ProportionalHeight
|
||||
)
|
||||
|
||||
# Character Formats
|
||||
# =================
|
||||
|
||||
self._cText = QTextCharFormat()
|
||||
self._cText.setBackground(QtTransparent)
|
||||
self._cText.setForeground(self._theme.text)
|
||||
|
||||
self._cCode = QTextCharFormat(self._cText)
|
||||
self._cCode.setForeground(self._theme.code)
|
||||
|
||||
self._cKeyword = QTextCharFormat(self._cText)
|
||||
self._cKeyword.setForeground(self._theme.keyword)
|
||||
|
||||
self._cTag = QTextCharFormat(self._cText)
|
||||
self._cTag.setForeground(self._theme.tag)
|
||||
|
||||
self._cOptional = QTextCharFormat(self._cText)
|
||||
self._cOptional.setForeground(self._theme.optional)
|
||||
self._charFmt = QTextCharFormat()
|
||||
self._charFmt.setBackground(QtTransparent)
|
||||
self._charFmt.setForeground(self._theme.text)
|
||||
|
||||
self._init = True
|
||||
|
||||
@@ -193,6 +178,13 @@ class ToQTextDocument(Tokenizer):
|
||||
|
||||
# Styles
|
||||
bFmt = QTextBlockFormat(self._blockFmt)
|
||||
if tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD):
|
||||
bFmt.setTopMargin(self._mMeta[0])
|
||||
bFmt.setBottomMargin(self._mMeta[1])
|
||||
elif tType == BlockTyp.SEP:
|
||||
bFmt.setTopMargin(self._mSep[0])
|
||||
bFmt.setBottomMargin(self._mSep[1])
|
||||
|
||||
if tStyle is not None:
|
||||
if tStyle & BlockFmt.LEFT:
|
||||
bFmt.setAlignment(QtAlignLeft)
|
||||
@@ -220,9 +212,9 @@ class ToQTextDocument(Tokenizer):
|
||||
if tStyle & BlockFmt.IND_T:
|
||||
bFmt.setTextIndent(self._tIndent)
|
||||
|
||||
if tType == BlockTyp.TEXT:
|
||||
if tType in (BlockTyp.TEXT, BlockTyp.COMMENT, BlockTyp.KEYWORD):
|
||||
newBlock(cursor, bFmt)
|
||||
self._insertFragments(tText, tFormat, cursor, self._cText)
|
||||
self._insertFragments(tText, tFormat, cursor, self._charFmt)
|
||||
|
||||
elif tType in self.L_HEADINGS:
|
||||
bFmt, cFmt = self._genHeadStyle(tType, nHead, bFmt)
|
||||
@@ -230,23 +222,12 @@ class ToQTextDocument(Tokenizer):
|
||||
cursor.insertText(tText.replace(nwHeadFmt.BR, "\n"), cFmt)
|
||||
|
||||
elif tType == BlockTyp.SEP:
|
||||
sFmt = QTextBlockFormat(bFmt)
|
||||
sFmt.setTopMargin(self._mSep[0])
|
||||
sFmt.setBottomMargin(self._mSep[1])
|
||||
newBlock(cursor, sFmt)
|
||||
cursor.insertText(tText, self._cText)
|
||||
newBlock(cursor, bFmt)
|
||||
cursor.insertText(tText, self._charFmt)
|
||||
|
||||
elif tType == BlockTyp.SKIP:
|
||||
newBlock(cursor, bFmt)
|
||||
cursor.insertText(nwUnicode.U_NBSP, self._cText)
|
||||
|
||||
elif tType == BlockTyp.COMMENT:
|
||||
newBlock(cursor, bFmt)
|
||||
self._insertFragments(tText, tFormat, cursor, self._cText)
|
||||
|
||||
elif tType == BlockTyp.KEYWORD:
|
||||
newBlock(cursor, bFmt)
|
||||
self._insertKeywords(tText, cursor)
|
||||
cursor.insertText(nwUnicode.U_NBSP, self._charFmt)
|
||||
|
||||
self._document.blockSignals(False)
|
||||
|
||||
@@ -281,12 +262,13 @@ class ToQTextDocument(Tokenizer):
|
||||
|
||||
for key, index in self._usedNotes.items():
|
||||
if content := self._footnotes.get(key):
|
||||
cFmt = QTextCharFormat(self._cCode)
|
||||
cFmt = QTextCharFormat(self._charFmt)
|
||||
cFmt.setForeground(self._theme.code)
|
||||
cFmt.setAnchor(True)
|
||||
cFmt.setAnchorNames([f"footnote_{index}"])
|
||||
newBlock(cursor, self._blockFmt)
|
||||
cursor.insertText(f"{index}. ", cFmt)
|
||||
self._insertFragments(*content, cursor, self._cText)
|
||||
self._insertFragments(*content, cursor, self._charFmt)
|
||||
|
||||
self._document.blockSignals(False)
|
||||
|
||||
@@ -342,8 +324,21 @@ class ToQTextDocument(Tokenizer):
|
||||
cFmt.setForeground(color)
|
||||
elif fmt == TextFmt.COL_E:
|
||||
cFmt.setForeground(self._theme.text)
|
||||
elif fmt == TextFmt.ANM_B:
|
||||
cFmt.setAnchor(True)
|
||||
cFmt.setAnchorNames([data])
|
||||
elif fmt == TextFmt.ANM_E:
|
||||
cFmt.setAnchor(False)
|
||||
elif fmt == TextFmt.HRF_B:
|
||||
cFmt.setFontUnderline(True)
|
||||
cFmt.setAnchor(True)
|
||||
cFmt.setAnchorHref(data)
|
||||
elif fmt == TextFmt.HRF_E:
|
||||
cFmt.setFontUnderline(False)
|
||||
cFmt.setAnchor(False)
|
||||
elif fmt == TextFmt.FNOTE:
|
||||
xFmt = QTextCharFormat(self._cCode)
|
||||
xFmt = QTextCharFormat(self._charFmt)
|
||||
xFmt.setForeground(self._theme.code)
|
||||
xFmt.setVerticalAlignment(QtVAlignSuper)
|
||||
if data in self._footnotes:
|
||||
index = len(self._usedNotes) + 1
|
||||
@@ -363,33 +358,6 @@ class ToQTextDocument(Tokenizer):
|
||||
|
||||
return
|
||||
|
||||
def _insertKeywords(self, text: str, cursor: QTextCursor) -> None:
|
||||
"""Apply Markdown formatting to keywords."""
|
||||
valid, bits, _ = self._project.index.scanThis("@"+text)
|
||||
if valid and bits:
|
||||
key = f"{self._localLookup(nwLabels.KEY_NAME[bits[0]])}: "
|
||||
cursor.insertText(key, self._cKeyword)
|
||||
if (num := len(bits)) > 1:
|
||||
if bits[0] == nwKeyWords.TAG_KEY:
|
||||
one, two = self._project.index.parseValue(bits[1])
|
||||
cFmt = QTextCharFormat(self._cTag)
|
||||
cFmt.setAnchor(True)
|
||||
cFmt.setAnchorNames([f"tag_{one}".lower()])
|
||||
cursor.insertText(one, cFmt)
|
||||
if two:
|
||||
cursor.insertText(" | ", self._cText)
|
||||
cursor.insertText(two, self._cOptional)
|
||||
else:
|
||||
for n, bit in enumerate(bits[1:], 2):
|
||||
cFmt = QTextCharFormat(self._cTag)
|
||||
cFmt.setFontUnderline(True)
|
||||
cFmt.setAnchor(True)
|
||||
cFmt.setAnchorHref(f"#tag_{bit}".lower())
|
||||
cursor.insertText(bit, cFmt)
|
||||
if n < num:
|
||||
cursor.insertText(", ", self._cText)
|
||||
return
|
||||
|
||||
def _genHeadStyle(self, hType: BlockTyp, nHead: int, rFmt: QTextBlockFormat) -> T_TextStyle:
|
||||
"""Generate a heading style set."""
|
||||
mTop, mBottom = self._mHead.get(hType, (0.0, 0.0))
|
||||
@@ -398,11 +366,11 @@ class ToQTextDocument(Tokenizer):
|
||||
bFmt.setTopMargin(mTop)
|
||||
bFmt.setBottomMargin(mBottom)
|
||||
|
||||
self._cTitle = QTextCharFormat(self._cText)
|
||||
self._cTitle = QTextCharFormat(self._charFmt)
|
||||
self._cTitle.setFontWeight(self._bold if self._boldHeads else self._normal)
|
||||
|
||||
hCol = self._colorHeads and hType != BlockTyp.TITLE
|
||||
cFmt = QTextCharFormat(self._cText)
|
||||
cFmt = QTextCharFormat(self._charFmt)
|
||||
cFmt.setForeground(self._theme.head if hCol else self._theme.text)
|
||||
cFmt.setFontWeight(self._bold if self._boldHeads else self._normal)
|
||||
cFmt.setFontPointSize(self._sHead.get(hType, 1.0))
|
||||
|
||||
Reference in New Issue
Block a user