python-pptx2 2.13.0__py3-none-any.whl → 2.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pptx2/__init__.py +1 -1
- pptx2/math.py +89 -37
- pptx2/mathml2omml.py +1163 -0
- pptx2/oxml/ns.py +1 -0
- pptx2/oxml/slide.py +2 -2
- pptx2/oxml/text.py +1 -0
- pptx2/parts/slide.py +9 -5
- pptx2/shapes/shapetree.py +3 -3
- pptx2/skill/SKILL.md +1 -1
- pptx2/skill/references/math.md +6 -4
- pptx2/text/text.py +3 -4
- {python_pptx2-2.13.0.dist-info → python_pptx2-2.14.0.dist-info}/METADATA +3 -3
- {python_pptx2-2.13.0.dist-info → python_pptx2-2.14.0.dist-info}/RECORD +17 -16
- {python_pptx2-2.13.0.dist-info → python_pptx2-2.14.0.dist-info}/WHEEL +0 -0
- {python_pptx2-2.13.0.dist-info → python_pptx2-2.14.0.dist-info}/entry_points.txt +0 -0
- {python_pptx2-2.13.0.dist-info → python_pptx2-2.14.0.dist-info}/licenses/LICENSE +0 -0
- {python_pptx2-2.13.0.dist-info → python_pptx2-2.14.0.dist-info}/top_level.txt +0 -0
pptx2/__init__.py
CHANGED
pptx2/math.py
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
PowerPoint stores editable equations as Office Math (OMML) wrapped in an
|
|
4
4
|
``a14:m`` marker inside a DrawingML paragraph. This module does not compile
|
|
5
|
-
LaTeX itself:
|
|
6
|
-
|
|
5
|
+
LaTeX itself: ``latex2mathml`` turns the fragment into MathML and the
|
|
6
|
+
bundled ``pptx2.mathml2omml`` port (from mathml2omml-plus / ECMA-376 §7.1)
|
|
7
|
+
turns that into Office Math.
|
|
7
8
|
|
|
8
|
-
Install the
|
|
9
|
+
Install the LaTeX front-end with::
|
|
9
10
|
|
|
10
11
|
pip install "python-pptx2[math]"
|
|
11
12
|
"""
|
|
@@ -17,6 +18,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
17
18
|
|
|
18
19
|
from lxml import etree
|
|
19
20
|
|
|
21
|
+
from pptx2.mathml2omml import convert as mathml_to_omml
|
|
20
22
|
from pptx2.oxml import parse_xml
|
|
21
23
|
from pptx2.oxml.ns import nsuri, qn
|
|
22
24
|
|
|
@@ -26,6 +28,8 @@ if TYPE_CHECKING:
|
|
|
26
28
|
|
|
27
29
|
A14_NS = nsuri("a14")
|
|
28
30
|
M_NS = nsuri("m")
|
|
31
|
+
W_NS = nsuri("w")
|
|
32
|
+
MC_NS = nsuri("mc")
|
|
29
33
|
|
|
30
34
|
_WRAPPER_PATTERNS = (
|
|
31
35
|
re.compile(r"^\s*\$\$(.*)\$\$\s*$", re.DOTALL),
|
|
@@ -47,9 +51,15 @@ _DISPLAY_JC = {
|
|
|
47
51
|
"justify": "centerGroup",
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
_SLD_OPEN_RE = re.compile(
|
|
55
|
+
r"^(<\?xml[^>]*\?>\s*)?(<p:sld\b)([^>]*)(>)",
|
|
56
|
+
re.DOTALL,
|
|
57
|
+
)
|
|
58
|
+
_IGNORABLE_RE = re.compile(r'\smc:Ignorable="([^"]*)"')
|
|
59
|
+
|
|
50
60
|
|
|
51
61
|
class MathBackendUnavailable(ImportError):
|
|
52
|
-
"""Raised when the optional LaTeX →
|
|
62
|
+
"""Raised when the optional LaTeX → MathML converter is not installed."""
|
|
53
63
|
|
|
54
64
|
|
|
55
65
|
def strip_math_delimiters(source: str) -> str:
|
|
@@ -72,13 +82,13 @@ def latex_to_omml(latex: str) -> str:
|
|
|
72
82
|
if not fragment:
|
|
73
83
|
raise ValueError("latex must be a non-empty math fragment")
|
|
74
84
|
|
|
75
|
-
converter
|
|
85
|
+
converter = _import_latex2mathml()
|
|
76
86
|
try:
|
|
77
87
|
mathml = converter.convert(fragment)
|
|
78
88
|
except Exception as exc:
|
|
79
89
|
raise ValueError(f"LaTeX could not be converted to MathML: {exc}") from exc
|
|
80
90
|
try:
|
|
81
|
-
omml =
|
|
91
|
+
omml = mathml_to_omml(mathml)
|
|
82
92
|
except Exception as exc:
|
|
83
93
|
raise ValueError(f"MathML could not be converted to Office Math: {exc}") from exc
|
|
84
94
|
if not isinstance(omml, str) or not omml.strip():
|
|
@@ -94,7 +104,10 @@ def office_math_element(
|
|
|
94
104
|
) -> CT_OfficeMathMarker:
|
|
95
105
|
"""Return an ``a14:m`` element ready to append to a DrawingML paragraph."""
|
|
96
106
|
payload = _omml_payload(latex_to_omml(latex), display=display, align=align)
|
|
97
|
-
xml =
|
|
107
|
+
xml = (
|
|
108
|
+
f'<a14:m xmlns:a14="{A14_NS}" xmlns:m="{M_NS}" xmlns:w="{W_NS}">'
|
|
109
|
+
f"{payload}</a14:m>"
|
|
110
|
+
)
|
|
98
111
|
return parse_xml(xml)
|
|
99
112
|
|
|
100
113
|
|
|
@@ -105,7 +118,7 @@ def style_office_math(
|
|
|
105
118
|
color: Any = None,
|
|
106
119
|
font: str | None = None,
|
|
107
120
|
) -> None:
|
|
108
|
-
"""Write
|
|
121
|
+
"""Write size / color / typeface onto every ``m:r`` and ``m:ctrlPr``."""
|
|
109
122
|
if size_pt is None and color is None and font is None:
|
|
110
123
|
return
|
|
111
124
|
|
|
@@ -113,32 +126,70 @@ def style_office_math(
|
|
|
113
126
|
|
|
114
127
|
sz = None if size_pt is None else str(int(round(float(size_pt) * 100)))
|
|
115
128
|
rgb = None if color is None else str(coerce_color(color))
|
|
129
|
+
r_pr_xml = _drawing_rpr(sz, rgb, font)
|
|
116
130
|
|
|
117
131
|
for run in marker.iter(qn("m:r")):
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
_stamp_arpr(run, r_pr_xml, before=qn("m:t"))
|
|
133
|
+
for ctrl in marker.iter(qn("m:ctrlPr")):
|
|
134
|
+
_stamp_arpr(ctrl, r_pr_xml, before=None)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def prepare_slide_xml_for_math(xml: bytes) -> bytes:
|
|
138
|
+
"""Ensure the ``p:sld`` root declares math host namespaces.
|
|
139
|
+
|
|
140
|
+
PowerPoint requires ``xmlns:m``, ``xmlns:a14``, ``xmlns:mc`` and
|
|
141
|
+
``mc:Ignorable="a14"`` on the slide (MS-PPTX §2.2.8). Bare ``m:oMath``
|
|
142
|
+
without ``a14:m`` is stripped on open.
|
|
143
|
+
"""
|
|
144
|
+
match = _SLD_OPEN_RE.match(xml.decode("utf-8"))
|
|
145
|
+
if match is None:
|
|
146
|
+
return xml
|
|
147
|
+
decl, start, attrs, close = match.group(1) or "", match.group(2), match.group(3), match.group(4)
|
|
148
|
+
for prefix, uri in (
|
|
149
|
+
("xmlns:m", M_NS),
|
|
150
|
+
("xmlns:a14", A14_NS),
|
|
151
|
+
("xmlns:mc", MC_NS),
|
|
152
|
+
("xmlns:w", W_NS),
|
|
153
|
+
):
|
|
154
|
+
if f'{prefix}="' not in attrs:
|
|
155
|
+
attrs += f' {prefix}="{uri}"'
|
|
156
|
+
ignorable = _IGNORABLE_RE.search(attrs)
|
|
157
|
+
if ignorable is None:
|
|
158
|
+
attrs += ' mc:Ignorable="a14"'
|
|
159
|
+
else:
|
|
160
|
+
tokens = ignorable.group(1).split()
|
|
161
|
+
if "a14" not in tokens:
|
|
162
|
+
tokens.append("a14")
|
|
163
|
+
attrs = _IGNORABLE_RE.sub(f' mc:Ignorable="{" ".join(tokens)}"', attrs, count=1)
|
|
164
|
+
rewritten = decl + start + attrs + close + xml.decode("utf-8")[match.end() :]
|
|
165
|
+
return rewritten.encode("utf-8")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _drawing_rpr(sz: str | None, rgb: str | None, font: str | None) -> etree._Element:
|
|
169
|
+
attrs = {"dirty": "0"}
|
|
170
|
+
if sz is not None:
|
|
171
|
+
attrs["sz"] = sz
|
|
172
|
+
r_pr = etree.Element(qn("a:rPr"), attrs)
|
|
173
|
+
if rgb is not None:
|
|
174
|
+
solid = etree.SubElement(r_pr, qn("a:solidFill"))
|
|
175
|
+
etree.SubElement(solid, qn("a:srgbClr")).set("val", rgb)
|
|
176
|
+
if font is not None:
|
|
177
|
+
etree.SubElement(r_pr, qn("a:latin")).set("typeface", font)
|
|
178
|
+
return r_pr
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _stamp_arpr(parent: Any, template: etree._Element, before: str | None) -> None:
|
|
182
|
+
existing = parent.find(qn("a:rPr"))
|
|
183
|
+
stamped = etree.fromstring(etree.tostring(template))
|
|
184
|
+
if existing is not None:
|
|
185
|
+
parent.replace(existing, stamped)
|
|
186
|
+
return
|
|
187
|
+
if before is not None:
|
|
188
|
+
sibling = parent.find(before)
|
|
189
|
+
if sibling is not None:
|
|
190
|
+
sibling.addprevious(stamped)
|
|
191
|
+
return
|
|
192
|
+
parent.append(stamped)
|
|
142
193
|
|
|
143
194
|
|
|
144
195
|
def _omml_payload(omml: str, *, display: bool, align: str | None) -> str:
|
|
@@ -156,22 +207,23 @@ def _omml_payload(omml: str, *, display: bool, align: str | None) -> str:
|
|
|
156
207
|
)
|
|
157
208
|
|
|
158
209
|
|
|
159
|
-
def
|
|
210
|
+
def _import_latex2mathml():
|
|
160
211
|
try:
|
|
161
212
|
import latex2mathml.converter as latex2mathml_converter
|
|
162
|
-
import mathml2omml
|
|
163
213
|
except ImportError as exc:
|
|
164
214
|
raise MathBackendUnavailable(
|
|
165
|
-
"Native PowerPoint equations require
|
|
166
|
-
"
|
|
215
|
+
"Native PowerPoint equations require latex2mathml. "
|
|
216
|
+
"Install with: pip install 'python-pptx2[math]'"
|
|
167
217
|
) from exc
|
|
168
|
-
return latex2mathml_converter
|
|
218
|
+
return latex2mathml_converter
|
|
169
219
|
|
|
170
220
|
|
|
171
221
|
__all__ = (
|
|
172
222
|
"MathBackendUnavailable",
|
|
173
223
|
"latex_to_omml",
|
|
224
|
+
"mathml_to_omml",
|
|
174
225
|
"office_math_element",
|
|
226
|
+
"prepare_slide_xml_for_math",
|
|
175
227
|
"strip_math_delimiters",
|
|
176
228
|
"style_office_math",
|
|
177
229
|
)
|