Fix issue with global justify, and add indentation to DocX
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
# =======
|
||||
|
||||
|
||||
Reference in New Issue
Block a user