Fix issue with global justify, and add indentation to DocX

This commit is contained in:
Veronica Berglyd Olsen
2024-10-19 20:14:01 +02:00
parent a0ab507ed1
commit de6c3e9b45
6 changed files with 112 additions and 38 deletions
+63 -15
View File
@@ -110,7 +110,6 @@ S_HEAD2 = "Heading2"
S_HEAD3 = "Heading3"
S_HEAD4 = "Heading4"
S_SEP = "Separator"
S_FIND = "FirstLineIndent"
S_META = "TextMeta"
# Colours
@@ -132,10 +131,12 @@ class DocXParStyle(NamedTuple):
before: float | None = None
after: float | None = None
line: float | None = None
indentFirst: float | None = None
align: str | None = None
default: bool = False
level: int | None = None
color: str | None = None
bold: bool = False
class ToDocX(Tokenizer):
@@ -217,7 +218,8 @@ class ToDocX(Tokenizer):
"""Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case
# xText = self._xText
bIndent = self._fontSize * self._blockIndent
for tType, _, tText, tFormat, tStyle in self._tokens:
# Create Paragraph
@@ -232,8 +234,8 @@ class ToDocX(Tokenizer):
par.setAlignment("right")
elif tStyle & self.A_CENTRE:
par.setAlignment("center")
# elif tStyle & self.A_JUSTIFY:
# oStyle.setTextAlign("justify")
elif tStyle & self.A_JUSTIFY:
par.setAlignment("both")
if tStyle & self.A_PBB:
par.setPageBreakBefore(True)
@@ -245,17 +247,17 @@ class ToDocX(Tokenizer):
if tStyle & self.A_Z_TOPMRG:
par.setMarginTop(0.0)
# if tStyle & self.A_IND_L:
# oStyle.setMarginLeft(self._fBlockIndent)
# if tStyle & self.A_IND_R:
# oStyle.setMarginRight(self._fBlockIndent)
if tStyle & self.A_IND_T:
par.setIndentFirst(True)
if tStyle & self.A_IND_L:
par.setLeftMargin(bIndent)
if tStyle & self.A_IND_R:
par.setRightMargin(bIndent)
# Process Text Types
if tType == self.T_TEXT:
# Text indentation is processed here because there is a
# dedicated pre-defined style for it
# if tStyle & self.A_IND_T:
# else:
if self._doJustify and "\n" in tText:
par.overrideJustify(self._defaultAlign)
self._processFragments(par, S_NORM, tText, tFormat)
elif tType == self.T_TITLE:
@@ -595,6 +597,7 @@ class ToDocX(Tokenizer):
fSz2 = (nwStyles.H_SIZES[2] * fSz) if hScale else fSz
fSz3 = (nwStyles.H_SIZES[3] * fSz) if hScale else fSz
fSz4 = (nwStyles.H_SIZES[4] * fSz) if hScale else fSz
align = "both" if self._doJustify else "left"
# Add Normal Style
self._addParStyle(DocXParStyle(
@@ -605,6 +608,8 @@ class ToDocX(Tokenizer):
before=fSz * self._marginText[0],
after=fSz * self._marginText[1],
line=fSz * self._lineHeight,
indentFirst=fSz * self._firstWidth,
align=align,
))
# Add Title
@@ -618,6 +623,7 @@ class ToDocX(Tokenizer):
after=fSz * self._marginTitle[1],
line=fSz0 * self._lineHeight,
level=0,
bold=self._boldHeads,
))
# Add Heading 1
@@ -632,6 +638,7 @@ class ToDocX(Tokenizer):
line=fSz1 * self._lineHeight,
level=0,
color=COL_HEAD_L12 if hColor else None,
bold=self._boldHeads,
))
# Add Heading 2
@@ -646,6 +653,7 @@ class ToDocX(Tokenizer):
line=fSz2 * self._lineHeight,
level=1,
color=COL_HEAD_L12 if hColor else None,
bold=self._boldHeads,
))
# Add Heading 3
@@ -660,6 +668,7 @@ class ToDocX(Tokenizer):
line=fSz3 * self._lineHeight,
level=1,
color=COL_HEAD_L34 if hColor else None,
bold=self._boldHeads,
))
# Add Heading 4
@@ -674,6 +683,7 @@ class ToDocX(Tokenizer):
line=fSz4 * self._lineHeight,
level=1,
color=COL_HEAD_L34 if hColor else None,
bold=self._boldHeads,
))
# Add Separator
@@ -737,6 +747,8 @@ class ToDocX(Tokenizer):
xmlSubElem(rPr, _wTag("szCs"), attrib={_wTag("val"): str(int(2.0 * size))})
if style.color:
xmlSubElem(rPr, _wTag("color"), attrib={_wTag("val"): style.color})
if style.bold:
xmlSubElem(rPr, _wTag("b"))
self._styles[style.styleId] = style
@@ -746,8 +758,9 @@ class ToDocX(Tokenizer):
class DocXParagraph:
__slots__ = (
"_content", "_style", "_textAlign", "_topMargin", "_bottomMargin",
"_breakBefore", "_breakAfter",
"_content", "_style", "_textAlign",
"_topMargin", "_bottomMargin", "_leftMargin", "_rightMargin",
"_indentFirst", "_breakBefore", "_breakAfter",
)
def __init__(self) -> None:
@@ -756,6 +769,9 @@ class DocXParagraph:
self._textAlign: str | None = None
self._topMargin: float | None = None
self._bottomMargin: float | None = None
self._leftMargin: float | None = None
self._rightMargin: float | None = None
self._indentFirst = False
self._breakBefore = False
self._breakAfter = False
return
@@ -785,7 +801,7 @@ class DocXParagraph:
def setAlignment(self, value: str) -> None:
"""Set paragraph alignment."""
if value in ("left", "center", "right"):
if value in ("left", "center", "right", "both"):
self._textAlign = value
return
@@ -799,6 +815,21 @@ class DocXParagraph:
self._bottomMargin = value
return
def setLeftMargin(self, value: float) -> None:
"""Set left indent."""
self._leftMargin = value
return
def setRightMargin(self, value: float) -> None:
"""Set right line indent."""
self._rightMargin = value
return
def setIndentFirst(self, state: bool) -> None:
"""Set first line indent."""
self._indentFirst = state
return
def setPageBreakBefore(self, state: bool) -> None:
"""Set page break before flag."""
self._breakBefore = state
@@ -813,6 +844,12 @@ class DocXParagraph:
# Methods
##
def overrideJustify(self, default: str) -> None:
"""Override inherited justify setting if None is set."""
if self._textAlign is None:
self.setAlignment(default)
return
def addContent(self, run: ET.Element) -> None:
"""Add a run segment to the paragraph."""
self._content.append(run)
@@ -823,6 +860,15 @@ class DocXParagraph:
if style := self._style:
par = xmlSubElem(body, _wTag("p"))
# Values
indent = {}
if self._indentFirst and style.indentFirst is not None:
indent[_wTag("firstLine")] = str(int(20.0 * style.indentFirst))
if self._leftMargin is not None:
indent[_wTag("left")] = str(int(20.0 * self._leftMargin))
if self._rightMargin is not None:
indent[_wTag("right")] = str(int(20.0 * self._rightMargin))
# Paragraph
pPr = xmlSubElem(par, _wTag("pPr"))
xmlSubElem(pPr, _wTag("pStyle"), attrib={_wTag("val"): style.styleId})
@@ -834,6 +880,8 @@ class DocXParagraph:
})
if self._textAlign:
xmlSubElem(pPr, _wTag("jc"), attrib={_wTag("val"): self._textAlign})
if indent:
xmlSubElem(pPr, _wTag("ind"), attrib=indent)
# Text
if self._breakBefore:
+1 -1
View File
@@ -372,7 +372,7 @@ class ToHtml(Tokenizer):
"margin-top: {2:.2f}em; margin-bottom: {3:.2f}em;"
"}}"
).format(
"justify" if self._doJustify else "left",
"justify" if self._doJustify else self._defaultAlign,
round(100 * self._lineHeight),
mScale * self._marginText[0],
mScale * self._marginText[1],
+16 -15
View File
@@ -152,21 +152,22 @@ class Tokenizer(ABC):
# User Settings
self._textFont = QFont("Serif", 11) # Output text font
self._lineHeight = 1.15 # Line height in units of em
self._colorHeads = True # Colourise headings
self._scaleHeads = True # Scale headings to larger font size
self._boldHeads = True # Bold headings
self._blockIndent = 4.00 # Block indent in units of em
self._firstIndent = False # Enable first line indent
self._firstWidth = 1.40 # First line indent in units of em
self._indentFirst = False # Indent first paragraph
self._doJustify = False # Justify text
self._doBodyText = True # Include body text
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._lineHeight = 1.15 # Line height in units of em
self._colorHeads = True # Colourise headings
self._scaleHeads = True # Scale headings to larger font size
self._boldHeads = True # Bold headings
self._blockIndent = 4.00 # Block indent in units of em
self._firstIndent = False # Enable first line indent
self._firstWidth = 1.40 # First line indent in units of em
self._indentFirst = False # Indent first paragraph
self._doJustify = False # Justify text
self._doBodyText = True # Include body text
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
# Margins
self._marginTitle = nwStyles.T_MARGIN["H0"]
+10 -1
View File
@@ -327,7 +327,7 @@ class ToOdt(Tokenizer):
self._fLineHeight = f"{round(100 * self._lineHeight):d}%"
self._fBlockIndent = self._emToCm(self._blockIndent)
self._fTextIndent = self._emToCm(self._firstWidth)
self._textAlign = "justify" if self._doJustify else "left"
self._textAlign = "justify" if self._doJustify else self._defaultAlign
# Clear Errors
self._errData = []
@@ -457,6 +457,9 @@ class ToOdt(Tokenizer):
# Process Text Types
if tType == self.T_TEXT:
if self._doJustify and "\n" in tText:
oStyle.overrideJustify(self._defaultAlign)
# Text indentation is processed here because there is a
# dedicated pre-defined style for it
if tStyle & self.A_IND_T:
@@ -1313,6 +1316,12 @@ class ODTParagraphStyle:
# Methods
##
def overrideJustify(self, default: str) -> None:
"""Override inherited justify setting if None is set."""
if self._pAttr["text-align"][1] is None:
self.setTextAlign(default)
return
def checkNew(self, style: ODTParagraphStyle) -> bool:
"""Check if there are new settings in style that differ from
those in this object. Unset styles are ignored as they can be
+5 -5
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.5.2" hexVersion="0x020502f0" fileVersion="1.5" fileRevision="4" timeStamp="2024-09-17 15:33:20">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1956" autoCount="277" editTime="91001">
<novelWriterXML appVersion="2.6a1" hexVersion="0x020600a1" fileVersion="1.5" fileRevision="4" timeStamp="2024-10-19 20:03:34">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2073" autoCount="277" editTime="93036">
<name>Sample Project</name>
<author>Jane Smith</author>
</project>
@@ -46,7 +46,7 @@
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="233" wordCount="47" paraCount="2" cursorPos="275" />
<meta expanded="no" heading="H0" charCount="233" wordCount="47" paraCount="2" cursorPos="30" />
<name status="sf12341" import="ia857f0" active="yes">Page</name>
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -58,7 +58,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2953" wordCount="520" paraCount="15" cursorPos="0" />
<meta expanded="no" heading="H3" charCount="2953" wordCount="520" paraCount="15" cursorPos="66" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -66,7 +66,7 @@
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="0" />
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="357" />
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
+17 -1
View File
@@ -623,7 +623,7 @@ def testFmtToOdt_ConvertParagraphs(mockGUI):
'<office:text>'
'<text:h text:style-name="Heading_20_3" text:outline-level="3">Scene</text:h>'
'<text:p text:style-name="Text_20_body">Regular paragraph</text:p>'
'<text:p text:style-name="Text_20_body">with<text:line-break />break</text:p>'
'<text:p text:style-name="P7">with<text:line-break />break</text:p>'
'<text:p text:style-name="P7">Left Align</text:p>'
'</office:text>'
)
@@ -1127,6 +1127,22 @@ def testFmtToOdt_ODTParagraphStyle():
'</test>'
)
# Override Justify
# ================
aStyle = ODTParagraphStyle("test")
# When not set, override is possible
assert aStyle._pAttr["text-align"][1] is None
aStyle.overrideJustify("left")
assert aStyle._pAttr["text-align"][1] == "left"
# When explicitly set, not override
aStyle.setTextAlign("right")
assert aStyle._pAttr["text-align"][1] == "right"
aStyle.overrideJustify("left")
assert aStyle._pAttr["text-align"][1] == "right"
# Changes
# =======