python-pptx2 2.13.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 +152 -0
- pptx2/_color.py +75 -0
- pptx2/_slide_importer.py +597 -0
- pptx2/_svg.py +155 -0
- pptx2/_template_applier.py +292 -0
- pptx2/_textstyle.py +187 -0
- pptx2/accessibility.py +365 -0
- pptx2/action.py +270 -0
- pptx2/animation.py +2237 -0
- pptx2/api.py +49 -0
- pptx2/audit.py +258 -0
- pptx2/chart/__init__.py +0 -0
- pptx2/chart/analytics.py +381 -0
- pptx2/chart/axis.py +543 -0
- pptx2/chart/category.py +200 -0
- pptx2/chart/chart.py +670 -0
- pptx2/chart/data.py +864 -0
- pptx2/chart/datalabel.py +406 -0
- pptx2/chart/legend.py +86 -0
- pptx2/chart/marker.py +70 -0
- pptx2/chart/palettes.py +129 -0
- pptx2/chart/plot.py +462 -0
- pptx2/chart/point.py +101 -0
- pptx2/chart/quick_layouts.py +325 -0
- pptx2/chart/series.py +334 -0
- pptx2/chart/xlsx.py +272 -0
- pptx2/chart/xmlwriter.py +1845 -0
- pptx2/compose/__init__.py +28 -0
- pptx2/compose/from_spec.py +1094 -0
- pptx2/design/__init__.py +8 -0
- pptx2/design/components.py +607 -0
- pptx2/design/figures.py +389 -0
- pptx2/design/layout.py +370 -0
- pptx2/design/recipes.py +1967 -0
- pptx2/design/style.py +209 -0
- pptx2/design/tokens.py +915 -0
- pptx2/diagrams.py +754 -0
- pptx2/dml/__init__.py +0 -0
- pptx2/dml/chtfmt.py +40 -0
- pptx2/dml/color.py +496 -0
- pptx2/dml/effect.py +909 -0
- pptx2/dml/fill.py +691 -0
- pptx2/dml/line.py +287 -0
- pptx2/dml/picture.py +212 -0
- pptx2/dml/three_d.py +381 -0
- pptx2/enum/__init__.py +0 -0
- pptx2/enum/action.py +71 -0
- pptx2/enum/animation.py +31 -0
- pptx2/enum/base.py +218 -0
- pptx2/enum/chart.py +574 -0
- pptx2/enum/dml.py +740 -0
- pptx2/enum/lang.py +685 -0
- pptx2/enum/presentation.py +133 -0
- pptx2/enum/shapes.py +1029 -0
- pptx2/enum/text.py +230 -0
- pptx2/exc.py +42 -0
- pptx2/formats.py +139 -0
- pptx2/geometry.py +420 -0
- pptx2/inherit.py +109 -0
- pptx2/lint.py +2256 -0
- pptx2/math.py +177 -0
- pptx2/media.py +197 -0
- pptx2/opc/__init__.py +0 -0
- pptx2/opc/constants.py +332 -0
- pptx2/opc/oxml.py +188 -0
- pptx2/opc/package.py +762 -0
- pptx2/opc/packuri.py +109 -0
- pptx2/opc/serialized.py +296 -0
- pptx2/opc/shared.py +20 -0
- pptx2/opc/spec.py +45 -0
- pptx2/oxml/__init__.py +555 -0
- pptx2/oxml/action.py +53 -0
- pptx2/oxml/chart/__init__.py +0 -0
- pptx2/oxml/chart/axis.py +337 -0
- pptx2/oxml/chart/chart.py +481 -0
- pptx2/oxml/chart/datalabel.py +253 -0
- pptx2/oxml/chart/legend.py +72 -0
- pptx2/oxml/chart/marker.py +61 -0
- pptx2/oxml/chart/plot.py +365 -0
- pptx2/oxml/chart/series.py +425 -0
- pptx2/oxml/chart/shared.py +220 -0
- pptx2/oxml/coreprops.py +288 -0
- pptx2/oxml/dml/__init__.py +0 -0
- pptx2/oxml/dml/color.py +135 -0
- pptx2/oxml/dml/effect.py +213 -0
- pptx2/oxml/dml/fill.py +316 -0
- pptx2/oxml/dml/line.py +12 -0
- pptx2/oxml/dml/three_d.py +110 -0
- pptx2/oxml/ns.py +135 -0
- pptx2/oxml/presentation.py +313 -0
- pptx2/oxml/shapes/__init__.py +19 -0
- pptx2/oxml/shapes/autoshape.py +467 -0
- pptx2/oxml/shapes/connector.py +107 -0
- pptx2/oxml/shapes/graphfrm.py +347 -0
- pptx2/oxml/shapes/groupshape.py +329 -0
- pptx2/oxml/shapes/picture.py +270 -0
- pptx2/oxml/shapes/shared.py +577 -0
- pptx2/oxml/simpletypes.py +1027 -0
- pptx2/oxml/slide.py +563 -0
- pptx2/oxml/table.py +650 -0
- pptx2/oxml/text.py +815 -0
- pptx2/oxml/theme.py +36 -0
- pptx2/oxml/xmlchemy.py +717 -0
- pptx2/package.py +222 -0
- pptx2/parts/__init__.py +0 -0
- pptx2/parts/chart.py +95 -0
- pptx2/parts/coreprops.py +167 -0
- pptx2/parts/diagram.py +37 -0
- pptx2/parts/embeddedpackage.py +93 -0
- pptx2/parts/image.py +275 -0
- pptx2/parts/media.py +37 -0
- pptx2/parts/presentation.py +136 -0
- pptx2/parts/slide.py +371 -0
- pptx2/presentation.py +408 -0
- pptx2/py.typed +0 -0
- pptx2/render.py +586 -0
- pptx2/section.py +272 -0
- pptx2/shapes/__init__.py +26 -0
- pptx2/shapes/autoshape.py +442 -0
- pptx2/shapes/base.py +1078 -0
- pptx2/shapes/connector.py +297 -0
- pptx2/shapes/freeform.py +337 -0
- pptx2/shapes/graphfrm.py +316 -0
- pptx2/shapes/group.py +264 -0
- pptx2/shapes/picture.py +422 -0
- pptx2/shapes/placeholder.py +468 -0
- pptx2/shapes/shapetree.py +2027 -0
- pptx2/shared.py +82 -0
- pptx2/skill/SKILL.md +450 -0
- pptx2/skill/__init__.py +78 -0
- pptx2/skill/__main__.py +64 -0
- pptx2/skill/references/animations.md +189 -0
- pptx2/skill/references/basics.md +421 -0
- pptx2/skill/references/charts.md +254 -0
- pptx2/skill/references/compose.md +234 -0
- pptx2/skill/references/design.md +366 -0
- pptx2/skill/references/effects.md +249 -0
- pptx2/skill/references/end-to-end-deck.md +231 -0
- pptx2/skill/references/geometry-and-arrows.md +334 -0
- pptx2/skill/references/lint.md +275 -0
- pptx2/skill/references/math.md +86 -0
- pptx2/skill/references/picture-effects.md +129 -0
- pptx2/skill/references/render.md +151 -0
- pptx2/skill/references/smart-art.md +75 -0
- pptx2/skill/references/space-aware-authoring.md +249 -0
- pptx2/skill/references/tables.md +244 -0
- pptx2/skill/references/theme.md +127 -0
- pptx2/skill/references/three-d.md +109 -0
- pptx2/skill/references/transitions.md +100 -0
- pptx2/slide.py +1244 -0
- pptx2/smart_art.py +220 -0
- pptx2/spec.py +633 -0
- pptx2/table.py +1181 -0
- pptx2/table_styles.py +184 -0
- pptx2/templates/default.pptx +0 -0
- pptx2/templates/docx-icon.emf +0 -0
- pptx2/templates/generic-icon.emf +0 -0
- pptx2/templates/notes.xml +23 -0
- pptx2/templates/notesMaster.xml +352 -0
- pptx2/templates/pptx-icon.emf +0 -0
- pptx2/templates/theme.xml +321 -0
- pptx2/templates/xlsx-icon.emf +0 -0
- pptx2/text/__init__.py +0 -0
- pptx2/text/fonts.py +482 -0
- pptx2/text/layout.py +374 -0
- pptx2/text/text.py +1272 -0
- pptx2/theme.py +721 -0
- pptx2/types.py +36 -0
- pptx2/util.py +263 -0
- python_pptx2-2.13.0.dist-info/METADATA +351 -0
- python_pptx2-2.13.0.dist-info/RECORD +175 -0
- python_pptx2-2.13.0.dist-info/WHEEL +5 -0
- python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
- python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
- python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
pptx2/design/layout.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Build-time layout helpers — :class:`Grid` and :class:`Stack`.
|
|
2
|
+
|
|
3
|
+
These objects compute ``(left, top, width, height)`` rectangles so callers
|
|
4
|
+
don't eyeball EMU values when placing shapes. They never read or mutate
|
|
5
|
+
slide XML on their own; geometry is only applied to a shape when the
|
|
6
|
+
caller passes it to :meth:`Grid.place` / :meth:`Stack.place` or assigns
|
|
7
|
+
the returned :class:`Box` to the shape's geometry properties directly.
|
|
8
|
+
|
|
9
|
+
Example::
|
|
10
|
+
|
|
11
|
+
from pptx2 import Presentation
|
|
12
|
+
from pptx2.design.layout import Grid, Stack
|
|
13
|
+
from pptx2.util import Inches, Pt
|
|
14
|
+
|
|
15
|
+
prs = Presentation()
|
|
16
|
+
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
|
17
|
+
|
|
18
|
+
grid = Grid(slide, cols=12, rows=6, gutter=Pt(12), margin=Inches(0.5))
|
|
19
|
+
title_box = grid.cell(col=0, row=0, col_span=12, row_span=1)
|
|
20
|
+
|
|
21
|
+
stack = Stack(
|
|
22
|
+
direction="vertical", gap=Pt(8),
|
|
23
|
+
left=Inches(0.5), top=Inches(2),
|
|
24
|
+
width=Inches(9),
|
|
25
|
+
)
|
|
26
|
+
bullet_one = stack.next(height=Inches(0.5))
|
|
27
|
+
bullet_two = stack.next(height=Inches(0.5))
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import TYPE_CHECKING, NamedTuple, Tuple, Union
|
|
33
|
+
|
|
34
|
+
from pptx2.util import Emu, Length
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from pptx2.shapes.base import BaseShape
|
|
38
|
+
from pptx2.slide import Slide
|
|
39
|
+
|
|
40
|
+
MarginSpec = Union[
|
|
41
|
+
int,
|
|
42
|
+
Length,
|
|
43
|
+
Tuple[int, int],
|
|
44
|
+
Tuple[int, int, int, int],
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Box(NamedTuple):
|
|
49
|
+
"""A rectangular region expressed as ``(left, top, width, height)``.
|
|
50
|
+
|
|
51
|
+
All four members are :class:`~pptx2.util.Length` (EMU) instances and so
|
|
52
|
+
can be assigned directly to a shape's positional properties::
|
|
53
|
+
|
|
54
|
+
box = grid.cell(col=0, row=0, col_span=6)
|
|
55
|
+
shape.left, shape.top = box.left, box.top
|
|
56
|
+
shape.width, shape.height = box.width, box.height
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
left: Length
|
|
60
|
+
top: Length
|
|
61
|
+
width: Length
|
|
62
|
+
height: Length
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _slide_dimensions(slide: "Slide") -> tuple[Length, Length]:
|
|
66
|
+
"""Return ``(slide_width, slide_height)`` for `slide`.
|
|
67
|
+
|
|
68
|
+
Raises :class:`ValueError` if `slide` is not attached to a presentation
|
|
69
|
+
or if either dimension is unset on that presentation.
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
presentation = slide.part.package.presentation_part.presentation
|
|
73
|
+
except AttributeError as e:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"slide must be attached to a presentation to use a Grid; "
|
|
76
|
+
"add it via `prs.slides.add_slide(...)` first"
|
|
77
|
+
) from e
|
|
78
|
+
width, height = presentation.slide_width, presentation.slide_height
|
|
79
|
+
if width is None or height is None:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"slide width/height must be set on the presentation to use a Grid"
|
|
82
|
+
)
|
|
83
|
+
return width, height
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _apply_box(shape: "BaseShape", box: Box) -> "BaseShape":
|
|
87
|
+
shape.left, shape.top = box.left, box.top
|
|
88
|
+
shape.width, shape.height = box.width, box.height
|
|
89
|
+
return shape
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Grid:
|
|
93
|
+
"""A column/row grid spanning a slide's content area.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
slide : Slide
|
|
98
|
+
Host slide. Used only to read the parent presentation's slide
|
|
99
|
+
dimensions; the slide is not mutated.
|
|
100
|
+
cols : int
|
|
101
|
+
Number of columns. Must be >= 1.
|
|
102
|
+
rows : int, optional
|
|
103
|
+
Number of rows. Defaults to 1; cells implicitly span the full
|
|
104
|
+
slide height when only one row is requested.
|
|
105
|
+
gutter : Length, optional
|
|
106
|
+
Spacing between cells, applied between columns and between rows.
|
|
107
|
+
Defaults to 0 (no gutter).
|
|
108
|
+
margin : Length or tuple, optional
|
|
109
|
+
Outer margin. Either a single :class:`Length` (uniform on all
|
|
110
|
+
four sides), or a 2-tuple ``(vertical, horizontal)``, or a
|
|
111
|
+
4-tuple ``(top, right, bottom, left)``. Defaults to 0.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
slide: "Slide",
|
|
117
|
+
cols: int,
|
|
118
|
+
rows: int = 1,
|
|
119
|
+
gutter: int = 0,
|
|
120
|
+
margin: MarginSpec = 0,
|
|
121
|
+
):
|
|
122
|
+
slide_w, slide_h = _slide_dimensions(slide)
|
|
123
|
+
self._init_extent(cols, rows, gutter, margin, slide_w, slide_h, 0, 0)
|
|
124
|
+
self._slide_width = slide_w
|
|
125
|
+
self._slide_height = slide_h
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_box(
|
|
129
|
+
cls,
|
|
130
|
+
box: "Box | tuple[int, int, int, int]",
|
|
131
|
+
cols: int,
|
|
132
|
+
rows: int = 1,
|
|
133
|
+
gutter: int = 0,
|
|
134
|
+
margin: MarginSpec = 0,
|
|
135
|
+
) -> "Grid":
|
|
136
|
+
"""Return a grid that spans `box` rather than the whole slide.
|
|
137
|
+
|
|
138
|
+
`box` is any ``(left, top, width, height)`` sequence — a
|
|
139
|
+
:class:`Box`, a :class:`~pptx2.geometry.BBox`, or a plain
|
|
140
|
+
4-tuple — so a panel, card row, or content column can carry its own
|
|
141
|
+
grid without a slide reference::
|
|
142
|
+
|
|
143
|
+
panel = BBox.from_inches(0.75, 2.4, 11.8, 3.6)
|
|
144
|
+
grid = Grid.from_box(panel, cols=5, rows=2, gutter=Pt(12))
|
|
145
|
+
grid.place(card, col=2, row=1)
|
|
146
|
+
|
|
147
|
+
No slide is touched, so :attr:`slide_width`-style questions don't
|
|
148
|
+
arise; cells are positioned relative to `box`'s own origin.
|
|
149
|
+
"""
|
|
150
|
+
left, top, width, height = (int(v) for v in tuple(box))
|
|
151
|
+
grid = cls.__new__(cls)
|
|
152
|
+
grid._init_extent(cols, rows, gutter, margin, Emu(width), Emu(height), left, top)
|
|
153
|
+
grid._slide_width = None
|
|
154
|
+
grid._slide_height = None
|
|
155
|
+
return grid
|
|
156
|
+
|
|
157
|
+
def _init_extent(
|
|
158
|
+
self,
|
|
159
|
+
cols: int,
|
|
160
|
+
rows: int,
|
|
161
|
+
gutter: int,
|
|
162
|
+
margin: MarginSpec,
|
|
163
|
+
extent_w: Length,
|
|
164
|
+
extent_h: Length,
|
|
165
|
+
origin_left: int,
|
|
166
|
+
origin_top: int,
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Shared constructor body for the slide-spanning and box-spanning cases."""
|
|
169
|
+
if cols < 1:
|
|
170
|
+
raise ValueError("Grid.cols must be >= 1, got %r" % cols)
|
|
171
|
+
if rows < 1:
|
|
172
|
+
raise ValueError("Grid.rows must be >= 1, got %r" % rows)
|
|
173
|
+
|
|
174
|
+
self._cols = int(cols)
|
|
175
|
+
self._rows = int(rows)
|
|
176
|
+
self._gutter = Emu(int(gutter))
|
|
177
|
+
self._top_m, self._right_m, self._bottom_m, self._left_m = self._coerce_margin(
|
|
178
|
+
margin
|
|
179
|
+
)
|
|
180
|
+
self._origin_left = Emu(int(origin_left))
|
|
181
|
+
self._origin_top = Emu(int(origin_top))
|
|
182
|
+
|
|
183
|
+
usable_w = extent_w - self._left_m - self._right_m - self._gutter * (self._cols - 1)
|
|
184
|
+
usable_h = extent_h - self._top_m - self._bottom_m - self._gutter * (self._rows - 1)
|
|
185
|
+
if usable_w <= 0 or usable_h <= 0:
|
|
186
|
+
raise ValueError(
|
|
187
|
+
"grid margins+gutters consume the entire slide; reduce them"
|
|
188
|
+
)
|
|
189
|
+
# store as float so col_span math is exact; we round on emission
|
|
190
|
+
self._col_w = usable_w / self._cols
|
|
191
|
+
self._row_h = usable_h / self._rows
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def cols(self) -> int:
|
|
195
|
+
return self._cols
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def rows(self) -> int:
|
|
199
|
+
return self._rows
|
|
200
|
+
|
|
201
|
+
def cell(self, col: int = 0, row: int = 0, col_span: int = 1, row_span: int = 1) -> Box:
|
|
202
|
+
"""Return the :class:`Box` for the cell starting at (`col`, `row`).
|
|
203
|
+
|
|
204
|
+
`col_span` and `row_span` extend the cell across additional
|
|
205
|
+
columns/rows. Negative indices and out-of-bounds spans raise
|
|
206
|
+
:class:`IndexError`.
|
|
207
|
+
"""
|
|
208
|
+
if col < 0 or row < 0:
|
|
209
|
+
raise IndexError("col/row must be non-negative")
|
|
210
|
+
if col_span < 1 or row_span < 1:
|
|
211
|
+
raise IndexError("col_span/row_span must be >= 1")
|
|
212
|
+
if col + col_span > self._cols or row + row_span > self._rows:
|
|
213
|
+
raise IndexError(
|
|
214
|
+
"cell (col=%d, row=%d, col_span=%d, row_span=%d) exceeds "
|
|
215
|
+
"%dx%d grid" % (col, row, col_span, row_span, self._cols, self._rows)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
left = self._origin_left + self._left_m + (self._col_w + self._gutter) * col
|
|
219
|
+
top = self._origin_top + self._top_m + (self._row_h + self._gutter) * row
|
|
220
|
+
width = self._col_w * col_span + self._gutter * (col_span - 1)
|
|
221
|
+
height = self._row_h * row_span + self._gutter * (row_span - 1)
|
|
222
|
+
return Box(
|
|
223
|
+
Emu(int(round(left))),
|
|
224
|
+
Emu(int(round(top))),
|
|
225
|
+
Emu(int(round(width))),
|
|
226
|
+
Emu(int(round(height))),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def place(
|
|
230
|
+
self,
|
|
231
|
+
shape: "BaseShape",
|
|
232
|
+
col: int = 0,
|
|
233
|
+
row: int = 0,
|
|
234
|
+
col_span: int = 1,
|
|
235
|
+
row_span: int = 1,
|
|
236
|
+
) -> "BaseShape":
|
|
237
|
+
"""Move `shape` to the cell at (`col`, `row`) with the given span.
|
|
238
|
+
|
|
239
|
+
Returns the shape so calls can be chained.
|
|
240
|
+
"""
|
|
241
|
+
return _apply_box(shape, self.cell(col, row, col_span, row_span))
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _coerce_margin(margin: MarginSpec) -> Tuple[Length, Length, Length, Length]:
|
|
245
|
+
if isinstance(margin, (tuple, list)):
|
|
246
|
+
if len(margin) == 2:
|
|
247
|
+
v, h = margin
|
|
248
|
+
return Emu(int(v)), Emu(int(h)), Emu(int(v)), Emu(int(h))
|
|
249
|
+
if len(margin) == 4:
|
|
250
|
+
top, right, bottom, left = margin
|
|
251
|
+
return Emu(int(top)), Emu(int(right)), Emu(int(bottom)), Emu(int(left))
|
|
252
|
+
raise ValueError(
|
|
253
|
+
"margin tuple must have 2 or 4 elements, got %d" % len(margin)
|
|
254
|
+
)
|
|
255
|
+
m = Emu(int(margin))
|
|
256
|
+
return m, m, m, m
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Stack:
|
|
260
|
+
"""A linear stack of cells laid out vertically or horizontally.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
direction : str
|
|
265
|
+
``"vertical"`` (default) stacks downward; ``"horizontal"`` stacks
|
|
266
|
+
rightward.
|
|
267
|
+
gap : Length
|
|
268
|
+
Spacing between consecutive cells. Defaults to 0.
|
|
269
|
+
left, top : Length
|
|
270
|
+
Origin of the first cell. Default 0.
|
|
271
|
+
width, height : Length, optional
|
|
272
|
+
Cross-axis span. For a vertical stack, `width` is the cell width
|
|
273
|
+
and each call to :meth:`next` consumes `height`; for a horizontal
|
|
274
|
+
stack, `height` is the cell height and each call consumes `width`.
|
|
275
|
+
|
|
276
|
+
The stack maintains a running cursor that advances after every
|
|
277
|
+
:meth:`next` (or :meth:`place`) call. :meth:`reset` returns the
|
|
278
|
+
cursor to the origin.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
_AXES = ("vertical", "horizontal")
|
|
282
|
+
|
|
283
|
+
def __init__(
|
|
284
|
+
self,
|
|
285
|
+
direction: str = "vertical",
|
|
286
|
+
gap: int = 0,
|
|
287
|
+
left: int = 0,
|
|
288
|
+
top: int = 0,
|
|
289
|
+
width: int | None = None,
|
|
290
|
+
height: int | None = None,
|
|
291
|
+
):
|
|
292
|
+
if direction not in self._AXES:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
"direction must be 'vertical' or 'horizontal', got %r" % direction
|
|
295
|
+
)
|
|
296
|
+
self._direction = direction
|
|
297
|
+
self._gap = Emu(int(gap))
|
|
298
|
+
self._origin_left = Emu(int(left))
|
|
299
|
+
self._origin_top = Emu(int(top))
|
|
300
|
+
self._width = None if width is None else Emu(int(width))
|
|
301
|
+
self._height = None if height is None else Emu(int(height))
|
|
302
|
+
self._cursor = 0
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def direction(self) -> str:
|
|
306
|
+
return self._direction
|
|
307
|
+
|
|
308
|
+
def reset(self) -> None:
|
|
309
|
+
"""Reset the cursor so the next :meth:`next` call starts at the origin."""
|
|
310
|
+
self._cursor = 0
|
|
311
|
+
|
|
312
|
+
def next(self, *, width: int | None = None, height: int | None = None) -> Box:
|
|
313
|
+
"""Allocate the next cell and return its :class:`Box`.
|
|
314
|
+
|
|
315
|
+
For a vertical stack, `height` is required; `width` overrides the
|
|
316
|
+
stack-level default. For a horizontal stack, `width` is required;
|
|
317
|
+
`height` overrides the stack-level default.
|
|
318
|
+
"""
|
|
319
|
+
leading_gap = 0 if self._cursor == 0 else int(self._gap)
|
|
320
|
+
|
|
321
|
+
if self._direction == "vertical":
|
|
322
|
+
if height is None:
|
|
323
|
+
raise TypeError("vertical Stack.next() requires `height=`")
|
|
324
|
+
cell_h = int(height)
|
|
325
|
+
cell_w = self._width if width is None else int(width)
|
|
326
|
+
if cell_w is None:
|
|
327
|
+
raise TypeError(
|
|
328
|
+
"vertical Stack needs a `width` "
|
|
329
|
+
"(either on the constructor or as a per-cell override)"
|
|
330
|
+
)
|
|
331
|
+
self._cursor += leading_gap
|
|
332
|
+
box = Box(
|
|
333
|
+
self._origin_left,
|
|
334
|
+
Emu(self._origin_top + self._cursor),
|
|
335
|
+
Emu(cell_w),
|
|
336
|
+
Emu(cell_h),
|
|
337
|
+
)
|
|
338
|
+
self._cursor += cell_h
|
|
339
|
+
else:
|
|
340
|
+
if width is None:
|
|
341
|
+
raise TypeError("horizontal Stack.next() requires `width=`")
|
|
342
|
+
cell_w = int(width)
|
|
343
|
+
cell_h = self._height if height is None else int(height)
|
|
344
|
+
if cell_h is None:
|
|
345
|
+
raise TypeError(
|
|
346
|
+
"horizontal Stack needs a `height` "
|
|
347
|
+
"(either on the constructor or as a per-cell override)"
|
|
348
|
+
)
|
|
349
|
+
self._cursor += leading_gap
|
|
350
|
+
box = Box(
|
|
351
|
+
Emu(self._origin_left + self._cursor),
|
|
352
|
+
self._origin_top,
|
|
353
|
+
Emu(cell_w),
|
|
354
|
+
Emu(cell_h),
|
|
355
|
+
)
|
|
356
|
+
self._cursor += cell_w
|
|
357
|
+
return box
|
|
358
|
+
|
|
359
|
+
def place(
|
|
360
|
+
self,
|
|
361
|
+
shape: "BaseShape",
|
|
362
|
+
*,
|
|
363
|
+
width: int | None = None,
|
|
364
|
+
height: int | None = None,
|
|
365
|
+
) -> "BaseShape":
|
|
366
|
+
"""Allocate the next cell and apply it to `shape`.
|
|
367
|
+
|
|
368
|
+
Returns the shape so calls can be chained.
|
|
369
|
+
"""
|
|
370
|
+
return _apply_box(shape, self.next(width=width, height=height))
|