Add DocX text styles

This commit is contained in:
Veronica Berglyd Olsen
2024-10-18 18:35:56 +02:00
parent d60b29e0a2
commit 9bbebd2085
3 changed files with 188 additions and 15 deletions
+2 -2
View File
@@ -334,14 +334,14 @@ class NWBuildDocument:
bldObj.setStyles(self._build.getBool("html.addStyles"))
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
if isinstance(bldObj, ToOdt):
if isinstance(bldObj, (ToOdt, ToDocX)):
bldObj.setLanguage(self._project.data.language)
bldObj.setHeaderFormat(
self._build.getStr("odt.pageHeader"),
self._build.getInt("odt.pageCountOffset"),
)
if isinstance(bldObj, (ToOdt, ToQTextDocument)):
if isinstance(bldObj, (ToOdt, ToDocX, ToQTextDocument)):
scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
bldObj.setPageLayout(
+185 -11
View File
@@ -32,6 +32,7 @@ from zipfile import ZipFile
from novelwriter import __version__
from novelwriter.common import xmlIndent
from novelwriter.constants import nwStyles
from novelwriter.core.project import NWProject
from novelwriter.formats.tokenizer import Tokenizer
@@ -41,24 +42,29 @@ logger = logging.getLogger(__name__)
WORD_BASE = "application/vnd.openxmlformats-officedocument"
RELS_TYPE = "application/vnd.openxmlformats-package.relationships+xml"
REL_CORE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
REL_APP = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
REL_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
REL_BASE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
# Main XML NameSpaces
PROPS_NS = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types"
RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
XML_NS = {
"w": W_NS,
"cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
"dc": "http://purl.org/dc/elements/1.1/",
"dcterms": "http://purl.org/dc/terms/",
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
"dcterms": "http://purl.org/dc/terms/",
}
for ns, uri in XML_NS.items():
ET.register_namespace(ns, uri)
def _wTag(tag: str) -> str:
"""Assemble namespace and tag name for standard w namespace."""
return f"{{{W_NS}}}{tag}"
def _mkTag(ns: str, tag: str) -> str:
"""Assemble namespace and tag name."""
if uri := XML_NS.get(ns, ""):
@@ -90,12 +96,19 @@ class ToDocX(Tokenizer):
super().__init__(project)
# XML
self._dDoc = ET.Element("") # document.xml
self._dDoc = ET.Element("") # document.xml
self._dStyl = ET.Element("") # styles.xml
self._xBody = ET.Element("") # Text body
# Properties
self._headerFormat = ""
self._pageOffset = 0
# Internal
self._dLanguage = "en-GB"
self._fontFamily = "Liberation Serif"
self._fontSize = 12.0
self._dLanguage = "en-GB"
return
@@ -106,18 +119,41 @@ class ToDocX(Tokenizer):
def setLanguage(self, language: str | None) -> None:
"""Set language for the document."""
if language:
self._dLanguage = language.replace("_", "-")
self._dLanguage = language
return
def setPageLayout(
self, width: float, height: float, top: float, bottom: float, left: float, right: float
) -> None:
"""Set the document page size and margins in millimetres."""
return
def setHeaderFormat(self, format: str, offset: int) -> None:
"""Set the document header format."""
self._headerFormat = format.strip()
self._pageOffset = offset
return
##
# Class Methods
##
def _emToSz(self, scale: float) -> int:
return int()
def initDocument(self) -> None:
"""Initialises the DocX document structure."""
self._dDoc = ET.Element(_mkTag("w", "document"))
self._xBody = ET.SubElement(self._dDoc, _mkTag("w", "body"))
self._fontFamily = self._textFont.family()
self._fontSize = self._textFont.pointSizeF()
self._dDoc = ET.Element(_wTag("document"))
self._dStyl = ET.Element(_wTag("styles"))
self._xBody = ET.SubElement(self._dDoc, _wTag("body"))
self._defaultStyles()
self._useableStyles()
return
@@ -136,10 +172,10 @@ class ToDocX(Tokenizer):
"Id": "rId1", "Type": REL_CORE, "Target": "docProps/core.xml",
})
_addSingle(dRels, "Relationship", attrib={
"Id": "rId2", "Type": REL_APP, "Target": "docProps/app.xml",
"Id": "rId2", "Type": f"{REL_BASE}/extended-properties", "Target": "docProps/app.xml",
})
_addSingle(dRels, "Relationship", attrib={
"Id": "rId3", "Type": REL_DOC, "Target": "word/document.xml",
"Id": "rId3", "Type": f"{REL_BASE}/officeDocument", "Target": "word/document.xml",
})
# core.xml
@@ -169,6 +205,9 @@ class ToDocX(Tokenizer):
# document.xml.rels
dDRels = ET.Element("Relationships", attrib={"xmlns": RELS_NS})
_addSingle(dDRels, "Relationship", attrib={
"Id": "rId1", "Type": f"{REL_BASE}/styles", "Target": "styles.xml",
})
# [Content_Types].xml
dCont = ET.Element("Types", attrib={"xmlns": TYPES_NS})
@@ -198,6 +237,10 @@ class ToDocX(Tokenizer):
"PartName": "/word/document.xml",
"ContentType": f"{WORD_BASE}.wordprocessingml.document.main+xml",
})
_addSingle(dCont, "Override", attrib={
"PartName": "/word/styles.xml",
"ContentType": f"{WORD_BASE}.wordprocessingml.styles+xml",
})
def xmlToZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
with zipObj.open(name, mode="w") as fObj:
@@ -211,6 +254,137 @@ class ToDocX(Tokenizer):
xmlToZip("docProps/app.xml", dApp, outZip)
xmlToZip("word/_rels/document.xml.rels", dDRels, outZip)
xmlToZip("word/document.xml", self._dDoc, outZip)
xmlToZip("word/styles.xml", self._dStyl, outZip)
xmlToZip("[Content_Types].xml", dCont, outZip)
return
##
# Internal Functions
##
def _defaultStyles(self) -> None:
"""Set the default styles."""
xStyl = ET.SubElement(self._dStyl, _wTag("docDefaults"))
xRDef = ET.SubElement(xStyl, _wTag("rPrDefault"))
xPDef = ET.SubElement(xStyl, _wTag("pPrDefault"))
xRPr = ET.SubElement(xRDef, _wTag("rPr"))
xPPr = ET.SubElement(xPDef, _wTag("pPr"))
size = str(int(2.0 * self._fontSize))
line = str(int(2.0 * self._lineHeight * self._fontSize))
ET.SubElement(xRPr, _wTag("rFonts"), attrib={
_wTag("ascii"): self._fontFamily,
_wTag("hAnsi"): self._fontFamily,
_wTag("cs"): self._fontFamily,
})
ET.SubElement(xRPr, _wTag("sz"), attrib={_wTag("val"): size})
ET.SubElement(xRPr, _wTag("szCs"), attrib={_wTag("val"): size})
ET.SubElement(xRPr, _wTag("lang"), attrib={_wTag("val"): self._dLanguage})
ET.SubElement(xPPr, _wTag("spacing"), attrib={_wTag("line"): line})
return
def _useableStyles(self) -> None:
"""Set the usable styles."""
# Add Normal Style
self._addParStyle(
name="Normal",
styleId="Normal",
size=1.0,
default=True,
margins=self._marginText,
)
# Add Heading 1
hScale = self._scaleHeads
self._addParStyle(
name="Heading 1",
styleId="Heading1",
size=nwStyles.H_SIZES[1] if hScale else 1.0,
basedOn="Normal",
nextStyle="Normal",
margins=self._marginHead1,
level=0,
)
# Add Heading 2
hScale = self._scaleHeads
self._addParStyle(
name="Heading 2",
styleId="Heading2",
size=nwStyles.H_SIZES[2] if hScale else 1.0,
basedOn="Normal",
nextStyle="Normal",
margins=self._marginHead2,
level=1,
)
# Add Heading 3
hScale = self._scaleHeads
self._addParStyle(
name="Heading 3",
styleId="Heading3",
size=nwStyles.H_SIZES[3] if hScale else 1.0,
basedOn="Normal",
nextStyle="Normal",
margins=self._marginHead3,
level=1,
)
# Add Heading 4
hScale = self._scaleHeads
self._addParStyle(
name="Heading 4",
styleId="Heading4",
size=nwStyles.H_SIZES[4] if hScale else 1.0,
basedOn="Normal",
nextStyle="Normal",
margins=self._marginHead4,
level=1,
)
return
def _addParStyle(
self, *,
name: str,
styleId: str,
size: float,
basedOn: str | None = None,
nextStyle: str | None = None,
margins: tuple[float, float] | None = None,
default: bool = False,
level: int | None = None,
) -> None:
"""Add a paragraph style."""
sAttr = {}
sAttr[_wTag("type")] = "paragraph"
sAttr[_wTag("styleId")] = styleId
if default:
sAttr[_wTag("default")] = "1"
sz = str(int(2.0 * size * self._fontSize))
xStyl = ET.SubElement(self._dStyl, _wTag("style"), attrib=sAttr)
ET.SubElement(xStyl, _wTag("name"), attrib={_wTag("val"): name})
if basedOn:
ET.SubElement(xStyl, _wTag("basedOn"), attrib={_wTag("val"): basedOn})
if nextStyle:
ET.SubElement(xStyl, _wTag("next"), attrib={_wTag("val"): nextStyle})
if level is not None:
ET.SubElement(xStyl, _wTag("outlineLvl"), attrib={_wTag("val"): str(level)})
xPPr = ET.SubElement(xStyl, _wTag("pPr"))
if margins:
ET.SubElement(xPPr, _wTag("spacing"), attrib={
_wTag("before"): str(int(20.0 * margins[0])),
_wTag("after"): str(int(20.0 * margins[1])),
})
xRPr = ET.SubElement(xStyl, _wTag("rPr"))
ET.SubElement(xRPr, _wTag("sz"), attrib={_wTag("val"): sz})
ET.SubElement(xRPr, _wTag("szCs"), attrib={_wTag("val"): sz})
return
+1 -2
View File
@@ -258,8 +258,7 @@ class ToOdt(Tokenizer):
return
def setPageLayout(
self, width: int | float, height: int | float,
top: int | float, bottom: int | float, left: int | float, right: int | float
self, width: float, height: float, top: float, bottom: float, left: float, right: float
) -> None:
"""Set the document page size and margins in millimetres."""
self._mDocWidth = f"{width/10.0:.3f}cm"