Add DocX text styles
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user