4bbfdcceff
audio-surface.el / image-surface.el: own-core additive-synthesis WAV and
raster-PNG renderers (integer-only DSP, since EL has no floats), rendered
from learned engram signatures via a pluggable surface-profile
abstraction (surface-profile.el). audio-demo.el / image-demo.el are
drivers. NOTE: demo files hardcode absolute paths to this worktree's own
directory — will need a path fixup before landing.
elp/projector/ is a Python package the author's own README marks as
"STAGING/PROOF-OF-SHAPE — not the deliverable", superseded by the native
.el surface-profile work above; kept as a validated architecture proof.
Generated output (elp/faculty/{out,sig}, elp/projector/out,
__pycache__) intentionally excluded.
114 lines
5.2 KiB
Python
114 lines
5.2 KiB
Python
"""docx.py — the .docx surface projector: an OWN minimal OOXML emitter.
|
|
|
|
Own-the-core: a .docx is just a ZIP of a few XML parts (WordprocessingML). We
|
|
emit it with the standard library only — ``zipfile`` + string XML — no
|
|
python-docx, no external dependency. This proves a "richer structured format"
|
|
surface without importing anyone else's toolkit.
|
|
|
|
Parts emitted (the minimal valid set + a styles part for real headings):
|
|
[Content_Types].xml
|
|
_rels/.rels
|
|
word/_rels/document.xml.rels
|
|
word/styles.xml (Title / Heading1 / Heading2 / Normal)
|
|
word/document.xml (the content)
|
|
|
|
Like the markdown projector it reads only the IR's realized sentences; it
|
|
invents nothing. The surface differs, the faithful content does not.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import os
|
|
import sys
|
|
import zipfile
|
|
from xml.sax.saxutils import escape
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
from document_ir import DocumentIR # noqa: E402
|
|
from projectors.base import register # noqa: E402
|
|
|
|
_CONTENT_TYPES = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
<Default Extension="xml" ContentType="application/xml"/>
|
|
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
|
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
|
|
</Types>"""
|
|
|
|
_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
|
</Relationships>"""
|
|
|
|
_DOC_RELS = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
</Relationships>"""
|
|
|
|
_W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
|
|
_STYLES = f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
<w:styles xmlns:w="{_W}">
|
|
<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/>
|
|
<w:rPr><w:sz w:val="22"/></w:rPr></w:style>
|
|
<w:style w:type="paragraph" w:styleId="Title"><w:name w:val="Title"/>
|
|
<w:pPr><w:spacing w:after="240"/></w:pPr>
|
|
<w:rPr><w:b/><w:sz w:val="52"/></w:rPr></w:style>
|
|
<w:style w:type="paragraph" w:styleId="Subtitle"><w:name w:val="Subtitle"/>
|
|
<w:rPr><w:i/><w:sz w:val="28"/><w:color w:val="555555"/></w:rPr></w:style>
|
|
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/>
|
|
<w:pPr><w:spacing w:before="240" w:after="120"/><w:outlineLvl w:val="0"/></w:pPr>
|
|
<w:rPr><w:b/><w:sz w:val="34"/></w:rPr></w:style>
|
|
<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/>
|
|
<w:pPr><w:spacing w:before="200" w:after="100"/><w:outlineLvl w:val="1"/></w:pPr>
|
|
<w:rPr><w:b/><w:sz w:val="28"/></w:rPr></w:style>
|
|
</w:styles>"""
|
|
|
|
|
|
def _para(text: str, style: str | None = None) -> str:
|
|
ppr = f"<w:pPr><w:pStyle w:val=\"{style}\"/></w:pPr>" if style else ""
|
|
return (f"<w:p>{ppr}<w:r><w:t xml:space=\"preserve\">"
|
|
f"{escape(text)}</w:t></w:r></w:p>")
|
|
|
|
|
|
class DocxProjector:
|
|
surface = "docx"
|
|
media_type = ("application/vnd.openxmlformats-officedocument."
|
|
"wordprocessingml.document")
|
|
ext = "docx"
|
|
modality = "text"
|
|
|
|
def _document_xml(self, doc: DocumentIR) -> str:
|
|
body: list[str] = [_para(doc.title, "Title")]
|
|
if doc.subtitle:
|
|
body.append(_para(doc.subtitle, "Subtitle"))
|
|
abstract = doc.meta.get("abstract")
|
|
if abstract is not None and abstract.sentences:
|
|
body.append(_para(abstract.text()))
|
|
for sec in doc.sections:
|
|
style = "Heading1" if sec.level <= 1 else "Heading2"
|
|
body.append(_para(sec.heading, style))
|
|
for block in sec.blocks:
|
|
t = block.text()
|
|
if t:
|
|
body.append(_para(t))
|
|
return (f"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
|
|
f"<w:document xmlns:w=\"{_W}\"><w:body>"
|
|
+ "".join(body)
|
|
+ "<w:sectPr><w:pgSz w:w=\"12240\" w:h=\"15840\"/>"
|
|
"<w:pgMar w:top=\"1440\" w:right=\"1440\" w:bottom=\"1440\" "
|
|
"w:left=\"1440\"/></w:sectPr></w:body></w:document>")
|
|
|
|
def project(self, doc: DocumentIR) -> bytes:
|
|
buf = io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
|
|
z.writestr("[Content_Types].xml", _CONTENT_TYPES)
|
|
z.writestr("_rels/.rels", _RELS)
|
|
z.writestr("word/_rels/document.xml.rels", _DOC_RELS)
|
|
z.writestr("word/styles.xml", _STYLES)
|
|
z.writestr("word/document.xml", self._document_xml(doc))
|
|
return buf.getvalue()
|
|
|
|
|
|
register(DocxProjector())
|