python-pptx2 2.17.0__py3-none-any.whl → 2.18.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/_agent_friendly.py +136 -0
- pptx2/shapes/shapetree.py +213 -46
- pptx2/skill/references/basics.md +9 -0
- {python_pptx2-2.17.0.dist-info → python_pptx2-2.18.0.dist-info}/METADATA +1 -1
- {python_pptx2-2.17.0.dist-info → python_pptx2-2.18.0.dist-info}/RECORD +10 -9
- {python_pptx2-2.17.0.dist-info → python_pptx2-2.18.0.dist-info}/WHEEL +0 -0
- {python_pptx2-2.17.0.dist-info → python_pptx2-2.18.0.dist-info}/entry_points.txt +0 -0
- {python_pptx2-2.17.0.dist-info → python_pptx2-2.18.0.dist-info}/licenses/LICENSE +0 -0
- {python_pptx2-2.17.0.dist-info → python_pptx2-2.18.0.dist-info}/top_level.txt +0 -0
pptx2/__init__.py
CHANGED
pptx2/_agent_friendly.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Agent-friendly kwarg normalization for the one-call shape helpers.
|
|
2
|
+
|
|
3
|
+
Code generators (LLM-trained models among them) reliably spell keyword
|
|
4
|
+
arguments the way *other* libraries spell them — matplotlib's
|
|
5
|
+
``fontfamily``/``fontsize``/``ha``/``va``, CSS-flavored ``text-align``,
|
|
6
|
+
``colour`` — and die on ``TypeError: unexpected keyword argument``. This
|
|
7
|
+
module absorbs that whole error class for the ergonomic helpers
|
|
8
|
+
(``add_text`` / ``add_equation`` / ``add_arrow``) in three layers:
|
|
9
|
+
|
|
10
|
+
1. **Synonyms** — a fixed map from common spellings onto each canonical
|
|
11
|
+
argument (``halign`` → ``align``, ``font_size`` → ``size_pt`` …).
|
|
12
|
+
2. **Fuzzy matching** — a near-miss kwarg (``algn``, ``colr``) is resolved
|
|
13
|
+
to its closest canonical or synonym when unambiguous.
|
|
14
|
+
3. **Didactic errors** — anything still unknown raises a ``TypeError``
|
|
15
|
+
that names the closest candidate and lists every accepted kwarg, so a
|
|
16
|
+
model reading the traceback self-corrects in one step.
|
|
17
|
+
|
|
18
|
+
An alias may *substitute* for its canonical but never contradict it:
|
|
19
|
+
passing two different values for the same logical argument is a genuine
|
|
20
|
+
caller bug and still raises.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import difflib
|
|
26
|
+
|
|
27
|
+
# Canonical kwarg -> spellings other ecosystems use for the same thing.
|
|
28
|
+
SYNONYMS: dict[str, tuple[str, ...]] = {
|
|
29
|
+
"text": ("txt", "string", "content", "label", "caption", "value"),
|
|
30
|
+
"font": ("font_family", "fontfamily", "font_name", "fontname", "typeface", "family", "face"),
|
|
31
|
+
"size_pt": ("size", "font_size", "fontsize", "pt_size", "point_size"),
|
|
32
|
+
"align": ("halign", "ha", "horizontal_align", "horizontal_alignment", "text_align", "text_alignment"),
|
|
33
|
+
"anchor": ("valign", "va", "vertical_align", "vertical_alignment", "v_align"),
|
|
34
|
+
"color": (
|
|
35
|
+
"colour",
|
|
36
|
+
"font_color",
|
|
37
|
+
"font_colour",
|
|
38
|
+
"text_color",
|
|
39
|
+
"text_colour",
|
|
40
|
+
"fg_color",
|
|
41
|
+
"line_color",
|
|
42
|
+
"stroke_color",
|
|
43
|
+
),
|
|
44
|
+
"weight_pt": ("weight", "line_weight", "width_pt", "stroke_width"),
|
|
45
|
+
"bold": ("font_bold", "is_bold"),
|
|
46
|
+
"italic": ("font_italic", "is_italic", "oblique"),
|
|
47
|
+
"word_wrap": ("wrap", "wrap_text", "text_wrap"),
|
|
48
|
+
"margin_pt": ("margin", "padding", "padding_pt", "inset", "inset_pt"),
|
|
49
|
+
"latex": ("tex", "formula", "equation", "expression", "math"),
|
|
50
|
+
"left": ("x",),
|
|
51
|
+
"top": ("y",),
|
|
52
|
+
"width": ("w",),
|
|
53
|
+
"height": ("h",),
|
|
54
|
+
"start": ("begin", "start_point", "start_shape", "from", "source"),
|
|
55
|
+
"end": ("to", "end_point", "end_shape", "target"),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_ALIAS_TO_CANONICAL: dict[str, str] = {
|
|
59
|
+
alias: canonical
|
|
60
|
+
for canonical, aliases in SYNONYMS.items()
|
|
61
|
+
for alias in aliases
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_FUZZY_CUTOFF = 0.85
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _did_you_mean(name: str, candidates: list[str], known: "list[str] | tuple[str, ...]") -> str | None:
|
|
68
|
+
"""Return the canonical arg *name* points at, or None.
|
|
69
|
+
|
|
70
|
+
Near-misses that hit both a canonical and one of its own aliases
|
|
71
|
+
(``algn`` → ``align`` and ``halign``) are NOT ambiguous — they agree —
|
|
72
|
+
so candidates are collapsed to their canonical form before the
|
|
73
|
+
ambiguity check.
|
|
74
|
+
"""
|
|
75
|
+
if not candidates:
|
|
76
|
+
return None
|
|
77
|
+
matches = difflib.get_close_matches(name, candidates, n=3, cutoff=_FUZZY_CUTOFF)
|
|
78
|
+
distinct = set()
|
|
79
|
+
for m in matches:
|
|
80
|
+
canonical = m if m in known else _ALIAS_TO_CANONICAL.get(m)
|
|
81
|
+
if canonical in known:
|
|
82
|
+
distinct.add(canonical)
|
|
83
|
+
if len(distinct) == 1:
|
|
84
|
+
return distinct.pop()
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def absorb_agent_kwargs(
|
|
89
|
+
method: str,
|
|
90
|
+
kwargs: dict,
|
|
91
|
+
canonical_names: "list[str] | tuple[str, ...]",
|
|
92
|
+
) -> dict:
|
|
93
|
+
"""Map alias / near-miss *kwargs* onto canonical names; raise else.
|
|
94
|
+
|
|
95
|
+
*canonical_names* lists the kwargs the method actually understands
|
|
96
|
+
(including any already-bound explicit parameters). Returns a dict
|
|
97
|
+
containing only canonical names. A synonym that matches a canonical
|
|
98
|
+
the caller already supplied with a *different* value raises
|
|
99
|
+
``TypeError``; equal values are fine.
|
|
100
|
+
"""
|
|
101
|
+
known = list(canonical_names)
|
|
102
|
+
alias_space = [a for a in _ALIAS_TO_CANONICAL if a not in known]
|
|
103
|
+
candidates = known + alias_space
|
|
104
|
+
|
|
105
|
+
resolved: dict = {}
|
|
106
|
+
for name, value in kwargs.items():
|
|
107
|
+
if name in known or name in resolved:
|
|
108
|
+
if name in resolved and resolved[name] != value:
|
|
109
|
+
raise TypeError(
|
|
110
|
+
f"{method}(): got {name!r} twice with different values "
|
|
111
|
+
f"({resolved[name]!r} vs {value!r})"
|
|
112
|
+
)
|
|
113
|
+
resolved[name] = value
|
|
114
|
+
continue
|
|
115
|
+
canonical = _ALIAS_TO_CANONICAL.get(name)
|
|
116
|
+
if canonical is not None and canonical not in known:
|
|
117
|
+
# The alias resolves to an argument this method doesn't take
|
|
118
|
+
# (e.g. ``to=`` on add_text); fall through to the fuzzy/error
|
|
119
|
+
# path rather than silently dropping the value.
|
|
120
|
+
canonical = None
|
|
121
|
+
if canonical is None:
|
|
122
|
+
canonical = _did_you_mean(name, candidates, known)
|
|
123
|
+
if canonical is None:
|
|
124
|
+
raise TypeError(
|
|
125
|
+
f"{method}(): got an unexpected keyword argument {name!r}. "
|
|
126
|
+
f"Accepted: {', '.join(sorted(known))} "
|
|
127
|
+
f"(synonyms like font_family/halign/valign/colour are fine)"
|
|
128
|
+
)
|
|
129
|
+
if canonical in resolved and resolved[canonical] != value:
|
|
130
|
+
raise TypeError(
|
|
131
|
+
f"{method}(): {name!r} and the value already given for "
|
|
132
|
+
f"{canonical!r} disagree ({value!r} vs {resolved[canonical]!r}); "
|
|
133
|
+
f"pass one spelling only"
|
|
134
|
+
)
|
|
135
|
+
resolved[canonical] = value
|
|
136
|
+
return resolved
|
pptx2/shapes/shapetree.py
CHANGED
|
@@ -557,24 +557,6 @@ class _BaseShapes(ParentedElementProxy):
|
|
|
557
557
|
return BaseShapeFactory(shape_elm, self)
|
|
558
558
|
|
|
559
559
|
|
|
560
|
-
def _resolve_alias(method: str, arg: str, canonical, alias):
|
|
561
|
-
"""Return *canonical*, falling back to its *alias* kwarg (both nullable).
|
|
562
|
-
|
|
563
|
-
Code generators (matplotlib-trained models among them) habitually spell
|
|
564
|
-
``font`` as ``font_family`` and ``anchor`` as ``valign``; accepting the
|
|
565
|
-
alias removes a whole class of one-keyword-away TypeErrors. Passing
|
|
566
|
-
both with different values is a genuine caller bug and still raises.
|
|
567
|
-
"""
|
|
568
|
-
if canonical is not None:
|
|
569
|
-
if alias is not None and alias != canonical:
|
|
570
|
-
raise TypeError(
|
|
571
|
-
f"{method}(): pass either {arg}= or its alias, not two "
|
|
572
|
-
f"different values ({canonical!r} vs {alias!r})"
|
|
573
|
-
)
|
|
574
|
-
return canonical
|
|
575
|
-
return alias
|
|
576
|
-
|
|
577
|
-
|
|
578
560
|
class _BaseGroupShapes(_BaseShapes):
|
|
579
561
|
"""Base class for shape-trees that can add shapes."""
|
|
580
562
|
|
|
@@ -918,53 +900,115 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
918
900
|
*bbox_or_positional,
|
|
919
901
|
text: str = "",
|
|
920
902
|
font: str | None = None,
|
|
921
|
-
font_family: str | None = None,
|
|
922
903
|
size_pt: float | None = None,
|
|
923
904
|
bold: bool | None = None,
|
|
924
905
|
italic: bool | None = None,
|
|
925
906
|
color=None,
|
|
926
907
|
align: str | None = None,
|
|
927
908
|
anchor: str | None = None,
|
|
928
|
-
valign: str | None = None,
|
|
929
909
|
margin_pt: float | tuple[float, float, float, float] | None = None,
|
|
930
910
|
word_wrap: bool | None = True,
|
|
911
|
+
**kwargs,
|
|
931
912
|
) -> Shape:
|
|
932
913
|
"""Add a textbox carrying *text* with one-call styling.
|
|
933
914
|
|
|
934
|
-
Accepts either a :class:`~pptx2.geometry.BBox` positionally
|
|
935
|
-
|
|
915
|
+
Accepts either a :class:`~pptx2.geometry.BBox` positionally, the
|
|
916
|
+
four ``(left, top, width, height)`` lengths positionally, *or* the
|
|
917
|
+
same four as keyword arguments (``x`` / ``y`` / ``w`` / ``h``
|
|
918
|
+
synonyms included)::
|
|
936
919
|
|
|
937
920
|
slide.shapes.add_text(bbox, text="Hello", size_pt=24, bold=True,
|
|
938
921
|
color="#0B5CFF", align="center")
|
|
939
922
|
slide.shapes.add_text(Inches(1), Inches(2), Inches(4), Inches(1),
|
|
940
923
|
text="Hello")
|
|
924
|
+
slide.shapes.add_text(x=Inches(1), y=Inches(2), w=Inches(4),
|
|
925
|
+
h=Inches(1), text="Hello")
|
|
941
926
|
|
|
942
927
|
Keyword args:
|
|
943
928
|
|
|
929
|
+
* ``text`` — the string (``label`` / ``content`` synonyms work).
|
|
944
930
|
* ``font`` — typeface name (e.g. ``"Inter"``); ``None`` inherits.
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
* ``size_pt`` — font size in points; ``None`` inherits.
|
|
931
|
+
* ``size_pt`` — font size in points (synonyms: ``size``,
|
|
932
|
+
``font_size``, ``fontsize``).
|
|
948
933
|
* ``bold`` / ``italic`` — ``True``/``False``/``None``.
|
|
949
934
|
* ``color`` — any "color-like" (``"#RRGGBB"``, ``RGBColor``,
|
|
950
|
-
``(r, g, b)``).
|
|
935
|
+
``(r, g, b)``); ``colour`` / ``text_color`` synonyms work.
|
|
951
936
|
* ``align`` — ``"left"`` / ``"center"`` / ``"right"`` /
|
|
952
|
-
``"justify"``; ``
|
|
937
|
+
``"justify"``; ``halign`` is a synonym.
|
|
953
938
|
* ``anchor`` — vertical anchor: ``"top"`` / ``"middle"`` (also
|
|
954
|
-
``"mid"`` / ``"center"``) / ``"bottom"``; ``
|
|
955
|
-
|
|
939
|
+
``"mid"`` / ``"center"``) / ``"bottom"``; ``valign`` is a
|
|
940
|
+
synonym.
|
|
956
941
|
* ``margin_pt`` — uniform margin in points, or a 4-tuple
|
|
957
942
|
``(top, right, bottom, left)``.
|
|
958
943
|
* ``word_wrap`` — defaults to ``True``.
|
|
959
944
|
|
|
945
|
+
Misspelled or cross-library kwargs are absorbed when unambiguous
|
|
946
|
+
(``font_family`` → ``font``, ``fontsize`` → ``size_pt``, ``algn``
|
|
947
|
+
→ ``align`` …); anything unrecognizable raises a ``TypeError``
|
|
948
|
+
naming the accepted arguments.
|
|
949
|
+
|
|
960
950
|
Returns the textbox :class:`Shape` so further mutation works as
|
|
961
951
|
normal.
|
|
962
952
|
"""
|
|
953
|
+
from pptx2._agent_friendly import absorb_agent_kwargs
|
|
963
954
|
from pptx2._textstyle import apply_margins, apply_text_style, coerce_anchor
|
|
964
955
|
from pptx2.geometry import BBox
|
|
965
956
|
|
|
966
|
-
|
|
967
|
-
|
|
957
|
+
if kwargs:
|
|
958
|
+
kwargs = absorb_agent_kwargs(
|
|
959
|
+
"add_text",
|
|
960
|
+
kwargs,
|
|
961
|
+
(
|
|
962
|
+
"text", "font", "size_pt", "bold", "italic", "color",
|
|
963
|
+
"align", "anchor", "margin_pt", "word_wrap",
|
|
964
|
+
"left", "top", "width", "height",
|
|
965
|
+
),
|
|
966
|
+
)
|
|
967
|
+
geo_names = ("left", "top", "width", "height")
|
|
968
|
+
if any(g in kwargs for g in geo_names):
|
|
969
|
+
if bbox_or_positional:
|
|
970
|
+
raise TypeError(
|
|
971
|
+
"add_text(): got both positional geometry and geometry "
|
|
972
|
+
"keyword arguments; pass one form only"
|
|
973
|
+
)
|
|
974
|
+
if not all(g in kwargs for g in geo_names):
|
|
975
|
+
missing = [g for g in geo_names if g not in kwargs]
|
|
976
|
+
raise TypeError(
|
|
977
|
+
"add_text(): geometry keywords need all of "
|
|
978
|
+
f"left/top/width/height; missing {', '.join(missing)}"
|
|
979
|
+
)
|
|
980
|
+
bbox_or_positional = tuple(kwargs[g] for g in geo_names)
|
|
981
|
+
defaults = {
|
|
982
|
+
"text": "", "font": None, "size_pt": None, "bold": None,
|
|
983
|
+
"italic": None, "color": None, "align": None, "anchor": None,
|
|
984
|
+
"margin_pt": None, "word_wrap": True,
|
|
985
|
+
}
|
|
986
|
+
given = {
|
|
987
|
+
"text": text, "font": font, "size_pt": size_pt, "bold": bold,
|
|
988
|
+
"italic": italic, "color": color, "align": align,
|
|
989
|
+
"anchor": anchor, "margin_pt": margin_pt,
|
|
990
|
+
"word_wrap": word_wrap,
|
|
991
|
+
}
|
|
992
|
+
for name in defaults:
|
|
993
|
+
if name not in kwargs:
|
|
994
|
+
continue
|
|
995
|
+
value = kwargs[name]
|
|
996
|
+
if given[name] != defaults[name] and given[name] != value:
|
|
997
|
+
raise TypeError(
|
|
998
|
+
f"add_text(): {name!r} given twice with different "
|
|
999
|
+
f"values ({given[name]!r} vs {value!r})"
|
|
1000
|
+
)
|
|
1001
|
+
given[name] = value
|
|
1002
|
+
text = given["text"]
|
|
1003
|
+
font = given["font"]
|
|
1004
|
+
size_pt = given["size_pt"]
|
|
1005
|
+
bold = given["bold"]
|
|
1006
|
+
italic = given["italic"]
|
|
1007
|
+
color = given["color"]
|
|
1008
|
+
align = given["align"]
|
|
1009
|
+
anchor = given["anchor"]
|
|
1010
|
+
margin_pt = given["margin_pt"]
|
|
1011
|
+
word_wrap = given["word_wrap"]
|
|
968
1012
|
|
|
969
1013
|
if len(bbox_or_positional) == 1 and isinstance(bbox_or_positional[0], BBox):
|
|
970
1014
|
box = bbox_or_positional[0]
|
|
@@ -1009,21 +1053,21 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
1009
1053
|
def add_equation(
|
|
1010
1054
|
self,
|
|
1011
1055
|
*bbox_or_positional,
|
|
1012
|
-
latex: str,
|
|
1056
|
+
latex: str | None = None,
|
|
1013
1057
|
display: bool = True,
|
|
1014
1058
|
font: str | None = None,
|
|
1015
|
-
font_family: str | None = None,
|
|
1016
1059
|
size_pt: float | None = None,
|
|
1017
1060
|
color=None,
|
|
1018
1061
|
align: str | None = "center",
|
|
1019
1062
|
anchor: str | None = "middle",
|
|
1020
|
-
valign: str | None = None,
|
|
1021
1063
|
margin_pt: float | tuple[float, float, float, float] | None = None,
|
|
1064
|
+
**kwargs,
|
|
1022
1065
|
) -> Shape:
|
|
1023
1066
|
"""Add a text box containing a native PowerPoint equation from LaTeX.
|
|
1024
1067
|
|
|
1025
|
-
Accepts either a :class:`~pptx2.geometry.BBox
|
|
1026
|
-
``(left, top, width, height)
|
|
1068
|
+
Accepts either a :class:`~pptx2.geometry.BBox`, the four
|
|
1069
|
+
``(left, top, width, height)`` lengths positionally, or the same
|
|
1070
|
+
four as keyword arguments (``x`` / ``y`` / ``w`` / ``h`` included)::
|
|
1027
1071
|
|
|
1028
1072
|
slide.shapes.add_equation(bbox, latex=r"\\frac{a}{b}", size_pt=28)
|
|
1029
1073
|
slide.shapes.add_equation(
|
|
@@ -1036,16 +1080,74 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
1036
1080
|
equation editor.
|
|
1037
1081
|
|
|
1038
1082
|
Keyword args match :meth:`add_text` for *font* / *size_pt* /
|
|
1039
|
-
*color* / *align* / *anchor* / *margin_pt*
|
|
1040
|
-
``
|
|
1041
|
-
|
|
1042
|
-
|
|
1083
|
+
*color* / *align* / *anchor* / *margin_pt*, and *latex* accepts
|
|
1084
|
+
the ``tex`` / ``formula`` / ``equation`` synonyms. Misspelled or
|
|
1085
|
+
cross-library kwargs are absorbed when unambiguous (see
|
|
1086
|
+
:meth:`add_text`). *display* (default |True|) emits a display-math
|
|
1087
|
+
paragraph; set |False| for inline OMML.
|
|
1043
1088
|
"""
|
|
1089
|
+
from pptx2._agent_friendly import absorb_agent_kwargs
|
|
1044
1090
|
from pptx2._textstyle import apply_margins, coerce_align, coerce_anchor
|
|
1045
1091
|
from pptx2.geometry import BBox
|
|
1046
1092
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1093
|
+
if kwargs:
|
|
1094
|
+
kwargs = absorb_agent_kwargs(
|
|
1095
|
+
"add_equation",
|
|
1096
|
+
kwargs,
|
|
1097
|
+
(
|
|
1098
|
+
"latex", "display", "font", "size_pt", "color", "align",
|
|
1099
|
+
"anchor", "margin_pt", "left", "top", "width", "height",
|
|
1100
|
+
),
|
|
1101
|
+
)
|
|
1102
|
+
geo_names = ("left", "top", "width", "height")
|
|
1103
|
+
if any(g in kwargs for g in geo_names):
|
|
1104
|
+
if bbox_or_positional:
|
|
1105
|
+
raise TypeError(
|
|
1106
|
+
"add_equation(): got both positional geometry and "
|
|
1107
|
+
"geometry keyword arguments; pass one form only"
|
|
1108
|
+
)
|
|
1109
|
+
if not all(g in kwargs for g in geo_names):
|
|
1110
|
+
missing = [g for g in geo_names if g not in kwargs]
|
|
1111
|
+
raise TypeError(
|
|
1112
|
+
"add_equation(): geometry keywords need all of "
|
|
1113
|
+
f"left/top/width/height; missing {', '.join(missing)}"
|
|
1114
|
+
)
|
|
1115
|
+
bbox_or_positional = tuple(kwargs[g] for g in geo_names)
|
|
1116
|
+
defaults = {
|
|
1117
|
+
"latex": None, "display": True, "font": None, "size_pt": None,
|
|
1118
|
+
"color": None, "align": "center", "anchor": "middle",
|
|
1119
|
+
"margin_pt": None,
|
|
1120
|
+
}
|
|
1121
|
+
given = {
|
|
1122
|
+
"latex": latex, "display": display, "font": font,
|
|
1123
|
+
"size_pt": size_pt, "color": color, "align": align,
|
|
1124
|
+
"anchor": anchor, "margin_pt": margin_pt,
|
|
1125
|
+
}
|
|
1126
|
+
for name in defaults:
|
|
1127
|
+
if name not in kwargs:
|
|
1128
|
+
continue
|
|
1129
|
+
value = kwargs[name]
|
|
1130
|
+
if given[name] != defaults[name] and given[name] != value:
|
|
1131
|
+
raise TypeError(
|
|
1132
|
+
f"add_equation(): {name!r} given twice with different "
|
|
1133
|
+
f"values ({given[name]!r} vs {value!r})"
|
|
1134
|
+
)
|
|
1135
|
+
given[name] = value
|
|
1136
|
+
latex = given["latex"]
|
|
1137
|
+
display = given["display"]
|
|
1138
|
+
font = given["font"]
|
|
1139
|
+
size_pt = given["size_pt"]
|
|
1140
|
+
color = given["color"]
|
|
1141
|
+
align = given["align"]
|
|
1142
|
+
anchor = given["anchor"]
|
|
1143
|
+
margin_pt = given["margin_pt"]
|
|
1144
|
+
|
|
1145
|
+
if latex is None:
|
|
1146
|
+
raise TypeError(
|
|
1147
|
+
"add_equation() missing required argument: 'latex' "
|
|
1148
|
+
"(synonyms 'tex' / 'formula' / 'equation' accepted)"
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1049
1151
|
|
|
1050
1152
|
if len(bbox_or_positional) == 1 and isinstance(bbox_or_positional[0], BBox):
|
|
1051
1153
|
box = bbox_or_positional[0]
|
|
@@ -1080,8 +1182,8 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
1080
1182
|
|
|
1081
1183
|
def add_arrow(
|
|
1082
1184
|
self,
|
|
1083
|
-
start,
|
|
1084
|
-
end,
|
|
1185
|
+
start=None,
|
|
1186
|
+
end=None,
|
|
1085
1187
|
*,
|
|
1086
1188
|
head: str | None = "triangle",
|
|
1087
1189
|
tail: str | None = None,
|
|
@@ -1094,6 +1196,7 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
1094
1196
|
inset_pt: float = 0.0,
|
|
1095
1197
|
end_side: str = "auto",
|
|
1096
1198
|
start_side: str = "auto",
|
|
1199
|
+
**kwargs,
|
|
1097
1200
|
) -> Connector:
|
|
1098
1201
|
"""Add an arrow connector with proper arrowhead and inset routing.
|
|
1099
1202
|
|
|
@@ -1116,12 +1219,18 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
1116
1219
|
|
|
1117
1220
|
``style`` is ``"solid"`` / ``"dashed"`` / ``"dotted"``.
|
|
1118
1221
|
|
|
1119
|
-
``route`` is ``"straight"`` (default), ``"elbow"``, or
|
|
1120
|
-
|
|
1222
|
+
``route`` is ``"straight"`` (default), ``"elbow"``, or ``"curved"``
|
|
1223
|
+
— picks the underlying
|
|
1121
1224
|
:class:`~pptx2.enum.shapes.MSO_CONNECTOR_TYPE`.
|
|
1122
1225
|
|
|
1226
|
+
Keyword synonyms are absorbed: ``begin`` / ``from`` / ``source``
|
|
1227
|
+
for ``start``, ``to`` / ``target`` for ``end``, ``stroke_color`` /
|
|
1228
|
+
``colour`` for ``color``, ``weight`` / ``line_weight`` for
|
|
1229
|
+
``weight_pt`` — see :meth:`add_text` for the full aliasing story.
|
|
1230
|
+
|
|
1123
1231
|
Returns the :class:`Connector` so callers can tweak further.
|
|
1124
1232
|
"""
|
|
1233
|
+
from pptx2._agent_friendly import absorb_agent_kwargs
|
|
1125
1234
|
from pptx2._color import coerce_color
|
|
1126
1235
|
from pptx2.enum.dml import (
|
|
1127
1236
|
MSO_LINE_DASH_STYLE,
|
|
@@ -1131,6 +1240,64 @@ class _BaseGroupShapes(_BaseShapes):
|
|
|
1131
1240
|
from pptx2.enum.shapes import MSO_CONNECTOR_TYPE
|
|
1132
1241
|
from pptx2.util import Pt
|
|
1133
1242
|
|
|
1243
|
+
if kwargs:
|
|
1244
|
+
kwargs = absorb_agent_kwargs(
|
|
1245
|
+
"add_arrow",
|
|
1246
|
+
kwargs,
|
|
1247
|
+
(
|
|
1248
|
+
"start", "end", "head", "tail", "head_size", "tail_size",
|
|
1249
|
+
"color", "weight_pt", "style", "route", "inset_pt",
|
|
1250
|
+
"end_side", "start_side",
|
|
1251
|
+
),
|
|
1252
|
+
)
|
|
1253
|
+
defaults = {
|
|
1254
|
+
"head": "triangle", "tail": None, "head_size": "medium",
|
|
1255
|
+
"tail_size": "medium", "color": None, "weight_pt": 1.5,
|
|
1256
|
+
"style": "solid", "route": "straight", "inset_pt": 0.0,
|
|
1257
|
+
"end_side": "auto", "start_side": "auto",
|
|
1258
|
+
}
|
|
1259
|
+
given = {
|
|
1260
|
+
"head": head, "tail": tail, "head_size": head_size,
|
|
1261
|
+
"tail_size": tail_size, "color": color, "weight_pt": weight_pt,
|
|
1262
|
+
"style": style, "route": route, "inset_pt": inset_pt,
|
|
1263
|
+
"end_side": end_side, "start_side": start_side,
|
|
1264
|
+
}
|
|
1265
|
+
for name in defaults:
|
|
1266
|
+
if name not in kwargs:
|
|
1267
|
+
continue
|
|
1268
|
+
value = kwargs[name]
|
|
1269
|
+
if given[name] != defaults[name] and given[name] != value:
|
|
1270
|
+
raise TypeError(
|
|
1271
|
+
f"add_arrow(): {name!r} given twice with different "
|
|
1272
|
+
f"values ({given[name]!r} vs {value!r})"
|
|
1273
|
+
)
|
|
1274
|
+
given[name] = value
|
|
1275
|
+
head = given["head"]
|
|
1276
|
+
tail = given["tail"]
|
|
1277
|
+
head_size = given["head_size"]
|
|
1278
|
+
tail_size = given["tail_size"]
|
|
1279
|
+
color = given["color"]
|
|
1280
|
+
weight_pt = given["weight_pt"]
|
|
1281
|
+
style = given["style"]
|
|
1282
|
+
route = given["route"]
|
|
1283
|
+
inset_pt = given["inset_pt"]
|
|
1284
|
+
end_side = given["end_side"]
|
|
1285
|
+
start_side = given["start_side"]
|
|
1286
|
+
# start/end synonyms (from=/to=/begin=/source=/target=)
|
|
1287
|
+
if "start" in kwargs:
|
|
1288
|
+
start = kwargs["start"]
|
|
1289
|
+
if "end" in kwargs:
|
|
1290
|
+
end = kwargs["end"]
|
|
1291
|
+
if start is None or end is None:
|
|
1292
|
+
raise TypeError(
|
|
1293
|
+
"add_arrow() missing required argument(s): "
|
|
1294
|
+
+ ", ".join(
|
|
1295
|
+
name for name, value in (("start", start), ("end", end))
|
|
1296
|
+
if value is None
|
|
1297
|
+
)
|
|
1298
|
+
+ " (synonyms begin/from/source and to/target accepted)"
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1134
1301
|
_CONNECTOR = {
|
|
1135
1302
|
"straight": MSO_CONNECTOR_TYPE.STRAIGHT,
|
|
1136
1303
|
"elbow": MSO_CONNECTOR_TYPE.ELBOW,
|
pptx2/skill/references/basics.md
CHANGED
|
@@ -449,3 +449,12 @@ print(slide.notes) # "" when the slide has no notes
|
|
|
449
449
|
`start_slide_index` claims every slide from that position to the deck
|
|
450
450
|
end. `move` raises `IndexError` out of range; `reorder` raises
|
|
451
451
|
`ValueError` unless given a clean permutation.
|
|
452
|
+
|
|
453
|
+
### Forgiving keyword arguments
|
|
454
|
+
|
|
455
|
+
The one-call helpers (`add_text`, `add_equation`, `add_arrow`) absorb
|
|
456
|
+
cross-library and near-miss kwargs, so the `font_family=` / `halign=` /
|
|
457
|
+
`fontsize=` / `colour` / `x,y,w,h` / `begin,to` habits of other
|
|
458
|
+
ecosystems just work. Synonyms substitute, never contradict (two
|
|
459
|
+
different values for the same logical argument still raise), and a
|
|
460
|
+
truly unknown kwarg raises a `TypeError` listing every accepted name.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-pptx2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.18.0
|
|
4
4
|
Summary: Create, read, and update PowerPoint 2007+ (.pptx) files. Fork of power-pptx / python-pptx, published as python-pptx2.
|
|
5
5
|
Author: Matěj Štágl
|
|
6
6
|
Author-email: stagl@wattlescript.org
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
pptx2/__init__.py,sha256=
|
|
1
|
+
pptx2/__init__.py,sha256=RLbdJA33KyGpbpK-EK8th847D5voP73QPHz9FaEVq2M,4076
|
|
2
|
+
pptx2/_agent_friendly.py,sha256=zysyr-CLIM6saCk7m-fp8CcMJOaaIMPbqpmWMOibBCA,5663
|
|
2
3
|
pptx2/_color.py,sha256=Ureg4dSEdHbwpVZ5eJIyt0kLx_W3mGftmznejth_oLQ,2889
|
|
3
4
|
pptx2/_slide_importer.py,sha256=Ot3lW20eS7fMKsaGaeqPyeS9zVzt5D1WkovwozWwbYY,27033
|
|
4
5
|
pptx2/_svg.py,sha256=pMURsUQaJ9-dqRl4nEZKt1VECNpA8Ghp4EwNRWcBG-c,5973
|
|
@@ -136,12 +137,12 @@ pptx2/shapes/graphfrm.py,sha256=mwA9Wb4WRKBy6sA7KzOZFl3aHlpN-TmWRcE-kiMoqRI,1189
|
|
|
136
137
|
pptx2/shapes/group.py,sha256=xlcGi3MAzBHX2IM16IATxRgw8NwsPFzTMVbRPYQ7OKg,11036
|
|
137
138
|
pptx2/shapes/picture.py,sha256=U7rwiy7OXn0RgFSEdh07bdhIplWsI82OjFk-mt975J0,14938
|
|
138
139
|
pptx2/shapes/placeholder.py,sha256=9tIeNscnV46hWyMr8ZSiZO3TZr-tt4kZgx6BRB0tCvc,17838
|
|
139
|
-
pptx2/shapes/shapetree.py,sha256=
|
|
140
|
+
pptx2/shapes/shapetree.py,sha256=m1tVELFeK7Ha7uEMU1vtzxHXAZzGyk5bgTmiR8tgWlQ,90442
|
|
140
141
|
pptx2/skill/SKILL.md,sha256=6w7sWflZvhYYIzBsXLjx2uVKqB0793ZTvoZ_SUfzQxg,23668
|
|
141
142
|
pptx2/skill/__init__.py,sha256=bUkfMETwf3K6J7GSE-tLQjdNziS_JceunComLiLIeGI,2665
|
|
142
143
|
pptx2/skill/__main__.py,sha256=ohMLGy1jbjKV5_FqY6aGj4tm_isJ6fznvqUKiE1H6s8,1777
|
|
143
144
|
pptx2/skill/references/animations.md,sha256=ca02l5XCtOQpD-uSCItQixGaW0yONIY0P55yy-T-J1Q,5875
|
|
144
|
-
pptx2/skill/references/basics.md,sha256=
|
|
145
|
+
pptx2/skill/references/basics.md,sha256=XhdtDY93gQ9_dzPCITsgORFVLZNBaEnSQLMuK06dDio,14583
|
|
145
146
|
pptx2/skill/references/charts.md,sha256=skCS4Kn1j49mlIQJYDAkcu2VHc3S1mzwqkGT3ZE94wg,8119
|
|
146
147
|
pptx2/skill/references/compose.md,sha256=AfFwqAyQ9ggR140laqRDIUE3OcmTtXpIZH1oPcTOunA,8088
|
|
147
148
|
pptx2/skill/references/design.md,sha256=ne3xVyIwqpkhEkd8XK74QEija040eHve5zBHBx9LeNo,11706
|
|
@@ -170,9 +171,9 @@ pptx2/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
170
171
|
pptx2/text/fonts.py,sha256=ECa9HuBfeTqOi-uJTz_bu7eurC7e1_64RTwDatxmkpk,16498
|
|
171
172
|
pptx2/text/layout.py,sha256=k8bqod6GyshHJtfoqFtH9W-eHTI43IB0e6SLT1ibSUU,12815
|
|
172
173
|
pptx2/text/text.py,sha256=LPqF1B4GSQNSnSlVMm1v7pgtHCymOJPTUG_Kdt5_ou4,55039
|
|
173
|
-
python_pptx2-2.
|
|
174
|
-
python_pptx2-2.
|
|
175
|
-
python_pptx2-2.
|
|
176
|
-
python_pptx2-2.
|
|
177
|
-
python_pptx2-2.
|
|
178
|
-
python_pptx2-2.
|
|
174
|
+
python_pptx2-2.18.0.dist-info/licenses/LICENSE,sha256=hLdud18ZPS9iSG3kBCBLiGYzj4A6zedCHiDejIDBXNg,1230
|
|
175
|
+
python_pptx2-2.18.0.dist-info/METADATA,sha256=iiglSWLjdpEaY77K99QiNh0yJ_jaDzrU9so-vNplp0k,14374
|
|
176
|
+
python_pptx2-2.18.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
177
|
+
python_pptx2-2.18.0.dist-info/entry_points.txt,sha256=UpD1_pA-2BPeN0iDeD-u4NZ_yC78TvBdWu3V1xLsSRE,105
|
|
178
|
+
python_pptx2-2.18.0.dist-info/top_level.txt,sha256=NDdhJ4rOySlDU-cw4g-bQMoZCiy41WszlRXmIiReSRs,6
|
|
179
|
+
python_pptx2-2.18.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|