Add DocX text styles
This commit is contained in:
@@ -334,14 +334,14 @@ class NWBuildDocument:
|
|||||||
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
||||||
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
|
bldObj.setReplaceUnicode(self._build.getBool("format.stripUnicode"))
|
||||||
|
|
||||||
if isinstance(bldObj, ToOdt):
|
if isinstance(bldObj, (ToOdt, ToDocX)):
|
||||||
bldObj.setLanguage(self._project.data.language)
|
bldObj.setLanguage(self._project.data.language)
|
||||||
bldObj.setHeaderFormat(
|
bldObj.setHeaderFormat(
|
||||||
self._build.getStr("odt.pageHeader"),
|
self._build.getStr("odt.pageHeader"),
|
||||||
self._build.getInt("odt.pageCountOffset"),
|
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)
|
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))
|
pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
|
||||||
bldObj.setPageLayout(
|
bldObj.setPageLayout(
|
||||||
|
|||||||
+185
-11
@@ -32,6 +32,7 @@ from zipfile import ZipFile
|
|||||||
|
|
||||||
from novelwriter import __version__
|
from novelwriter import __version__
|
||||||
from novelwriter.common import xmlIndent
|
from novelwriter.common import xmlIndent
|
||||||
|
from novelwriter.constants import nwStyles
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.formats.tokenizer import Tokenizer
|
from novelwriter.formats.tokenizer import Tokenizer
|
||||||
|
|
||||||
@@ -41,24 +42,29 @@ logger = logging.getLogger(__name__)
|
|||||||
WORD_BASE = "application/vnd.openxmlformats-officedocument"
|
WORD_BASE = "application/vnd.openxmlformats-officedocument"
|
||||||
RELS_TYPE = "application/vnd.openxmlformats-package.relationships+xml"
|
RELS_TYPE = "application/vnd.openxmlformats-package.relationships+xml"
|
||||||
REL_CORE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
|
REL_CORE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
|
||||||
REL_APP = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
|
REL_BASE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||||
REL_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
|
|
||||||
|
|
||||||
# Main XML NameSpaces
|
# Main XML NameSpaces
|
||||||
PROPS_NS = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
PROPS_NS = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||||
TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types"
|
TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types"
|
||||||
RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
|
RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
|
||||||
|
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||||
XML_NS = {
|
XML_NS = {
|
||||||
|
"w": W_NS,
|
||||||
"cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
|
"cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
|
||||||
"dc": "http://purl.org/dc/elements/1.1/",
|
"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",
|
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||||
|
"dcterms": "http://purl.org/dc/terms/",
|
||||||
}
|
}
|
||||||
for ns, uri in XML_NS.items():
|
for ns, uri in XML_NS.items():
|
||||||
ET.register_namespace(ns, uri)
|
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:
|
def _mkTag(ns: str, tag: str) -> str:
|
||||||
"""Assemble namespace and tag name."""
|
"""Assemble namespace and tag name."""
|
||||||
if uri := XML_NS.get(ns, ""):
|
if uri := XML_NS.get(ns, ""):
|
||||||
@@ -90,12 +96,19 @@ class ToDocX(Tokenizer):
|
|||||||
super().__init__(project)
|
super().__init__(project)
|
||||||
|
|
||||||
# XML
|
# 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
|
self._xBody = ET.Element("") # Text body
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
self._headerFormat = ""
|
||||||
|
self._pageOffset = 0
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
self._dLanguage = "en-GB"
|
self._fontFamily = "Liberation Serif"
|
||||||
|
self._fontSize = 12.0
|
||||||
|
self._dLanguage = "en-GB"
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -106,18 +119,41 @@ class ToDocX(Tokenizer):
|
|||||||
def setLanguage(self, language: str | None) -> None:
|
def setLanguage(self, language: str | None) -> None:
|
||||||
"""Set language for the document."""
|
"""Set language for the document."""
|
||||||
if language:
|
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
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Class Methods
|
# Class Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def _emToSz(self, scale: float) -> int:
|
||||||
|
return int()
|
||||||
|
|
||||||
def initDocument(self) -> None:
|
def initDocument(self) -> None:
|
||||||
"""Initialises the DocX document structure."""
|
"""Initialises the DocX document structure."""
|
||||||
|
|
||||||
self._dDoc = ET.Element(_mkTag("w", "document"))
|
self._fontFamily = self._textFont.family()
|
||||||
self._xBody = ET.SubElement(self._dDoc, _mkTag("w", "body"))
|
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
|
return
|
||||||
|
|
||||||
@@ -136,10 +172,10 @@ class ToDocX(Tokenizer):
|
|||||||
"Id": "rId1", "Type": REL_CORE, "Target": "docProps/core.xml",
|
"Id": "rId1", "Type": REL_CORE, "Target": "docProps/core.xml",
|
||||||
})
|
})
|
||||||
_addSingle(dRels, "Relationship", attrib={
|
_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={
|
_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
|
# core.xml
|
||||||
@@ -169,6 +205,9 @@ class ToDocX(Tokenizer):
|
|||||||
|
|
||||||
# document.xml.rels
|
# document.xml.rels
|
||||||
dDRels = ET.Element("Relationships", attrib={"xmlns": RELS_NS})
|
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
|
# [Content_Types].xml
|
||||||
dCont = ET.Element("Types", attrib={"xmlns": TYPES_NS})
|
dCont = ET.Element("Types", attrib={"xmlns": TYPES_NS})
|
||||||
@@ -198,6 +237,10 @@ class ToDocX(Tokenizer):
|
|||||||
"PartName": "/word/document.xml",
|
"PartName": "/word/document.xml",
|
||||||
"ContentType": f"{WORD_BASE}.wordprocessingml.document.main+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:
|
def xmlToZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None:
|
||||||
with zipObj.open(name, mode="w") as fObj:
|
with zipObj.open(name, mode="w") as fObj:
|
||||||
@@ -211,6 +254,137 @@ class ToDocX(Tokenizer):
|
|||||||
xmlToZip("docProps/app.xml", dApp, outZip)
|
xmlToZip("docProps/app.xml", dApp, outZip)
|
||||||
xmlToZip("word/_rels/document.xml.rels", dDRels, outZip)
|
xmlToZip("word/_rels/document.xml.rels", dDRels, outZip)
|
||||||
xmlToZip("word/document.xml", self._dDoc, outZip)
|
xmlToZip("word/document.xml", self._dDoc, outZip)
|
||||||
|
xmlToZip("word/styles.xml", self._dStyl, outZip)
|
||||||
xmlToZip("[Content_Types].xml", dCont, outZip)
|
xmlToZip("[Content_Types].xml", dCont, outZip)
|
||||||
|
|
||||||
return
|
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
|
return
|
||||||
|
|
||||||
def setPageLayout(
|
def setPageLayout(
|
||||||
self, width: int | float, height: int | float,
|
self, width: float, height: float, top: float, bottom: float, left: float, right: float
|
||||||
top: int | float, bottom: int | float, left: int | float, right: int | float
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set the document page size and margins in millimetres."""
|
"""Set the document page size and margins in millimetres."""
|
||||||
self._mDocWidth = f"{width/10.0:.3f}cm"
|
self._mDocWidth = f"{width/10.0:.3f}cm"
|
||||||
|
|||||||
Reference in New Issue
Block a user