diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/oxml/shapes')
7 files changed, 1996 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/__init__.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/__init__.py new file mode 100644 index 00000000..37f8ef60 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/__init__.py @@ -0,0 +1,19 @@ +"""Base shape-related objects such as BaseShape.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing_extensions import TypeAlias + + from pptx.oxml.shapes.autoshape import CT_Shape + from pptx.oxml.shapes.connector import CT_Connector + from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame + from pptx.oxml.shapes.groupshape import CT_GroupShape + from pptx.oxml.shapes.picture import CT_Picture + + +ShapeElement: TypeAlias = ( + "CT_Connector | CT_GraphicalObjectFrame | CT_GroupShape | CT_Picture | CT_Shape" +) diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/autoshape.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/autoshape.py new file mode 100644 index 00000000..5d78f624 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/autoshape.py @@ -0,0 +1,455 @@ +# pyright: reportPrivateUsage=false + +"""lxml custom element classes for shape-related XML elements.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, cast + +from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, PP_PLACEHOLDER +from pptx.oxml import parse_xml +from pptx.oxml.ns import nsdecls +from pptx.oxml.shapes.shared import BaseShapeElement +from pptx.oxml.simpletypes import ( + ST_Coordinate, + ST_PositiveCoordinate, + XsdBoolean, + XsdString, +) +from pptx.oxml.text import CT_TextBody +from pptx.oxml.xmlchemy import ( + BaseOxmlElement, + OneAndOnlyOne, + OptionalAttribute, + RequiredAttribute, + ZeroOrMore, + ZeroOrOne, +) + +if TYPE_CHECKING: + from pptx.oxml.shapes.shared import ( + CT_ApplicationNonVisualDrawingProps, + CT_NonVisualDrawingProps, + CT_ShapeProperties, + ) + from pptx.util import Length + + +class CT_AdjPoint2D(BaseOxmlElement): + """`a:pt` custom element class.""" + + x: Length = RequiredAttribute("x", ST_Coordinate) # pyright: ignore[reportAssignmentType] + y: Length = RequiredAttribute("y", ST_Coordinate) # pyright: ignore[reportAssignmentType] + + +class CT_CustomGeometry2D(BaseOxmlElement): + """`a:custGeom` custom element class.""" + + get_or_add_pathLst: Callable[[], CT_Path2DList] + + _tag_seq = ("a:avLst", "a:gdLst", "a:ahLst", "a:cxnLst", "a:rect", "a:pathLst") + pathLst: CT_Path2DList | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:pathLst", successors=_tag_seq[6:] + ) + + +class CT_GeomGuide(BaseOxmlElement): + """`a:gd` custom element class. + + Defines a "guide", corresponding to a yellow diamond-shaped handle on an autoshape. + """ + + name: str = RequiredAttribute("name", XsdString) # pyright: ignore[reportAssignmentType] + fmla: str = RequiredAttribute("fmla", XsdString) # pyright: ignore[reportAssignmentType] + + +class CT_GeomGuideList(BaseOxmlElement): + """`a:avLst` custom element class.""" + + _add_gd: Callable[[], CT_GeomGuide] + + gd_lst: list[CT_GeomGuide] + + gd = ZeroOrMore("a:gd") + + +class CT_NonVisualDrawingShapeProps(BaseShapeElement): + """`p:cNvSpPr` custom element class.""" + + spLocks = ZeroOrOne("a:spLocks") + txBox: bool | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "txBox", XsdBoolean + ) + + +class CT_Path2D(BaseOxmlElement): + """`a:path` custom element class.""" + + _add_close: Callable[[], CT_Path2DClose] + _add_lnTo: Callable[[], CT_Path2DLineTo] + _add_moveTo: Callable[[], CT_Path2DMoveTo] + + close = ZeroOrMore("a:close", successors=()) + lnTo = ZeroOrMore("a:lnTo", successors=()) + moveTo = ZeroOrMore("a:moveTo", successors=()) + w: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w", ST_PositiveCoordinate + ) + h: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "h", ST_PositiveCoordinate + ) + + def add_close(self) -> CT_Path2DClose: + """Return a newly created `a:close` element. + + The new `a:close` element is appended to this `a:path` element. + """ + return self._add_close() + + def add_lnTo(self, x: Length, y: Length) -> CT_Path2DLineTo: + """Return a newly created `a:lnTo` subtree with end point *(x, y)*. + + The new `a:lnTo` element is appended to this `a:path` element. + """ + lnTo = self._add_lnTo() + pt = lnTo._add_pt() + pt.x, pt.y = x, y + return lnTo + + def add_moveTo(self, x: Length, y: Length): + """Return a newly created `a:moveTo` subtree with point `(x, y)`. + + The new `a:moveTo` element is appended to this `a:path` element. + """ + moveTo = self._add_moveTo() + pt = moveTo._add_pt() + pt.x, pt.y = x, y + return moveTo + + +class CT_Path2DClose(BaseOxmlElement): + """`a:close` custom element class.""" + + +class CT_Path2DLineTo(BaseOxmlElement): + """`a:lnTo` custom element class.""" + + _add_pt: Callable[[], CT_AdjPoint2D] + + pt = ZeroOrOne("a:pt", successors=()) + + +class CT_Path2DList(BaseOxmlElement): + """`a:pathLst` custom element class.""" + + _add_path: Callable[[], CT_Path2D] + + path = ZeroOrMore("a:path", successors=()) + + def add_path(self, w: Length, h: Length): + """Return a newly created `a:path` child element.""" + path = self._add_path() + path.w, path.h = w, h + return path + + +class CT_Path2DMoveTo(BaseOxmlElement): + """`a:moveTo` custom element class.""" + + _add_pt: Callable[[], CT_AdjPoint2D] + + pt = ZeroOrOne("a:pt", successors=()) + + +class CT_PresetGeometry2D(BaseOxmlElement): + """`a:prstGeom` custom element class.""" + + _add_avLst: Callable[[], CT_GeomGuideList] + _remove_avLst: Callable[[], None] + + avLst: CT_GeomGuideList | None = ZeroOrOne("a:avLst") # pyright: ignore[reportAssignmentType] + prst: MSO_AUTO_SHAPE_TYPE = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "prst", MSO_AUTO_SHAPE_TYPE + ) + + @property + def gd_lst(self) -> list[CT_GeomGuide]: + """Sequence of `a:gd` element children of `a:avLst`. Empty if none are present.""" + avLst = self.avLst + if avLst is None: + return [] + return avLst.gd_lst + + def rewrite_guides(self, guides: list[tuple[str, int]]): + """Replace any `a:gd` element children of `a:avLst` with ones forme from `guides`.""" + self._remove_avLst() + avLst = self._add_avLst() + for name, val in guides: + gd = avLst._add_gd() + gd.name = name + gd.fmla = "val %d" % val + + +class CT_Shape(BaseShapeElement): + """`p:sp` custom element class.""" + + get_or_add_txBody: Callable[[], CT_TextBody] + + nvSpPr: CT_ShapeNonVisual = OneAndOnlyOne("p:nvSpPr") # pyright: ignore[reportAssignmentType] + spPr: CT_ShapeProperties = OneAndOnlyOne("p:spPr") # pyright: ignore[reportAssignmentType] + txBody: CT_TextBody | None = ZeroOrOne("p:txBody", successors=("p:extLst",)) # pyright: ignore + + def add_path(self, w: Length, h: Length) -> CT_Path2D: + custGeom = self.spPr.custGeom + if custGeom is None: + raise ValueError("shape must be freeform") + pathLst = custGeom.get_or_add_pathLst() + return pathLst.add_path(w=w, h=h) + + def get_or_add_ln(self): + """Return the `a:ln` grandchild element, newly added if not present.""" + return self.spPr.get_or_add_ln() + + @property + def has_custom_geometry(self): + """True if this shape has custom geometry, i.e. is a freeform shape. + + A shape has custom geometry if it has a `p:spPr/a:custGeom` + descendant (instead of `p:spPr/a:prstGeom`). + """ + return self.spPr.custGeom is not None + + @property + def is_autoshape(self): + """True if this shape is an auto shape. + + A shape is an auto shape if it has a `a:prstGeom` element and does not have a txBox="1" + attribute on cNvSpPr. + """ + prstGeom = self.prstGeom + if prstGeom is None: + return False + return self.nvSpPr.cNvSpPr.txBox is not True + + @property + def is_textbox(self): + """True if this shape is a text box. + + A shape is a text box if it has a `txBox` attribute on cNvSpPr that resolves to |True|. + The default when the txBox attribute is missing is |False|. + """ + return self.nvSpPr.cNvSpPr.txBox is True + + @property + def ln(self): + """`a:ln` grand-child element or |None| if not present.""" + return self.spPr.ln + + @staticmethod + def new_autoshape_sp( + id_: int, name: str, prst: str, left: int, top: int, width: int, height: int + ) -> CT_Shape: + """Return a new `p:sp` element tree configured as a base auto shape.""" + xml = ( + "<p:sp %s>\n" + " <p:nvSpPr>\n" + ' <p:cNvPr id="%s" name="%s"/>\n' + " <p:cNvSpPr/>\n" + " <p:nvPr/>\n" + " </p:nvSpPr>\n" + " <p:spPr>\n" + " <a:xfrm>\n" + ' <a:off x="%s" y="%s"/>\n' + ' <a:ext cx="%s" cy="%s"/>\n' + " </a:xfrm>\n" + ' <a:prstGeom prst="%s">\n' + " <a:avLst/>\n" + " </a:prstGeom>\n" + " </p:spPr>\n" + " <p:style>\n" + ' <a:lnRef idx="1">\n' + ' <a:schemeClr val="accent1"/>\n' + " </a:lnRef>\n" + ' <a:fillRef idx="3">\n' + ' <a:schemeClr val="accent1"/>\n' + " </a:fillRef>\n" + ' <a:effectRef idx="2">\n' + ' <a:schemeClr val="accent1"/>\n' + " </a:effectRef>\n" + ' <a:fontRef idx="minor">\n' + ' <a:schemeClr val="lt1"/>\n' + " </a:fontRef>\n" + " </p:style>\n" + " <p:txBody>\n" + ' <a:bodyPr rtlCol="0" anchor="ctr"/>\n' + " <a:lstStyle/>\n" + " <a:p>\n" + ' <a:pPr algn="ctr"/>\n' + " </a:p>\n" + " </p:txBody>\n" + "</p:sp>" % (nsdecls("a", "p"), "%d", "%s", "%d", "%d", "%d", "%d", "%s") + ) % (id_, name, left, top, width, height, prst) + return cast(CT_Shape, parse_xml(xml)) + + @staticmethod + def new_freeform_sp(shape_id: int, name: str, x: int, y: int, cx: int, cy: int): + """Return new `p:sp` element tree configured as freeform shape. + + The returned shape has a `a:custGeom` subtree but no paths in its + path list. + """ + xml = ( + "<p:sp %s>\n" + " <p:nvSpPr>\n" + ' <p:cNvPr id="%s" name="%s"/>\n' + " <p:cNvSpPr/>\n" + " <p:nvPr/>\n" + " </p:nvSpPr>\n" + " <p:spPr>\n" + " <a:xfrm>\n" + ' <a:off x="%s" y="%s"/>\n' + ' <a:ext cx="%s" cy="%s"/>\n' + " </a:xfrm>\n" + " <a:custGeom>\n" + " <a:avLst/>\n" + " <a:gdLst/>\n" + " <a:ahLst/>\n" + " <a:cxnLst/>\n" + ' <a:rect l="l" t="t" r="r" b="b"/>\n' + " <a:pathLst/>\n" + " </a:custGeom>\n" + " </p:spPr>\n" + " <p:style>\n" + ' <a:lnRef idx="1">\n' + ' <a:schemeClr val="accent1"/>\n' + " </a:lnRef>\n" + ' <a:fillRef idx="3">\n' + ' <a:schemeClr val="accent1"/>\n' + " </a:fillRef>\n" + ' <a:effectRef idx="2">\n' + ' <a:schemeClr val="accent1"/>\n' + " </a:effectRef>\n" + ' <a:fontRef idx="minor">\n' + ' <a:schemeClr val="lt1"/>\n' + " </a:fontRef>\n" + " </p:style>\n" + " <p:txBody>\n" + ' <a:bodyPr rtlCol="0" anchor="ctr"/>\n' + " <a:lstStyle/>\n" + " <a:p>\n" + ' <a:pPr algn="ctr"/>\n' + " </a:p>\n" + " </p:txBody>\n" + "</p:sp>" % (nsdecls("a", "p"), "%d", "%s", "%d", "%d", "%d", "%d") + ) % (shape_id, name, x, y, cx, cy) + return cast(CT_Shape, parse_xml(xml)) + + @staticmethod + def new_placeholder_sp( + id_: int, name: str, ph_type: PP_PLACEHOLDER, orient: str, sz, idx + ) -> CT_Shape: + """Return a new `p:sp` element tree configured as a placeholder shape.""" + sp = cast( + CT_Shape, + parse_xml( + f"<p:sp {nsdecls('a', 'p')}>\n" + f" <p:nvSpPr>\n" + f' <p:cNvPr id="{id_}" name="{name}"/>\n' + f" <p:cNvSpPr>\n" + f' <a:spLocks noGrp="1"/>\n' + f" </p:cNvSpPr>\n" + f" <p:nvPr/>\n" + f" </p:nvSpPr>\n" + f" <p:spPr/>\n" + f"</p:sp>" + ), + ) + + ph = sp.nvSpPr.nvPr.get_or_add_ph() + ph.type = ph_type + ph.idx = idx + ph.orient = orient + ph.sz = sz + + placeholder_types_that_have_a_text_frame = ( + PP_PLACEHOLDER.TITLE, + PP_PLACEHOLDER.CENTER_TITLE, + PP_PLACEHOLDER.SUBTITLE, + PP_PLACEHOLDER.BODY, + PP_PLACEHOLDER.OBJECT, + ) + + if ph_type in placeholder_types_that_have_a_text_frame: + sp.append(CT_TextBody.new()) + + return sp + + @staticmethod + def new_textbox_sp(id_, name, left, top, width, height): + """Return a new `p:sp` element tree configured as a base textbox shape.""" + tmpl = CT_Shape._textbox_sp_tmpl() + xml = tmpl % (id_, name, left, top, width, height) + sp = parse_xml(xml) + return sp + + @property + def prst(self): + """Value of `prst` attribute of `a:prstGeom` element or |None| if not present.""" + prstGeom = self.prstGeom + if prstGeom is None: + return None + return prstGeom.prst + + @property + def prstGeom(self) -> CT_PresetGeometry2D: + """Reference to `a:prstGeom` child element. + + |None| if this shape doesn't have one, for example, if it's a placeholder shape. + """ + return self.spPr.prstGeom + + def _new_txBody(self): + return CT_TextBody.new_p_txBody() + + @staticmethod + def _textbox_sp_tmpl(): + return ( + "<p:sp %s>\n" + " <p:nvSpPr>\n" + ' <p:cNvPr id="%s" name="%s"/>\n' + ' <p:cNvSpPr txBox="1"/>\n' + " <p:nvPr/>\n" + " </p:nvSpPr>\n" + " <p:spPr>\n" + " <a:xfrm>\n" + ' <a:off x="%s" y="%s"/>\n' + ' <a:ext cx="%s" cy="%s"/>\n' + " </a:xfrm>\n" + ' <a:prstGeom prst="rect">\n' + " <a:avLst/>\n" + " </a:prstGeom>\n" + " <a:noFill/>\n" + " </p:spPr>\n" + " <p:txBody>\n" + ' <a:bodyPr wrap="none">\n' + " <a:spAutoFit/>\n" + " </a:bodyPr>\n" + " <a:lstStyle/>\n" + " <a:p/>\n" + " </p:txBody>\n" + "</p:sp>" % (nsdecls("a", "p"), "%d", "%s", "%d", "%d", "%d", "%d") + ) + + +class CT_ShapeNonVisual(BaseShapeElement): + """`p:nvSpPr` custom element class.""" + + cNvPr: CT_NonVisualDrawingProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "p:cNvPr" + ) + cNvSpPr: CT_NonVisualDrawingShapeProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "p:cNvSpPr" + ) + nvPr: CT_ApplicationNonVisualDrawingProps = ( # pyright: ignore[reportAssignmentType] + OneAndOnlyOne("p:nvPr") + ) diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/connector.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/connector.py new file mode 100644 index 00000000..91261f78 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/connector.py @@ -0,0 +1,107 @@ +"""lxml custom element classes for XML elements related to the Connector shape.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from pptx.oxml import parse_xml +from pptx.oxml.ns import nsdecls +from pptx.oxml.shapes.shared import BaseShapeElement +from pptx.oxml.simpletypes import ST_DrawingElementId, XsdUnsignedInt +from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne, RequiredAttribute, ZeroOrOne + +if TYPE_CHECKING: + from pptx.oxml.shapes.shared import CT_ShapeProperties + + +class CT_Connection(BaseShapeElement): + """A `a:stCxn` or `a:endCxn` element. + + Specifies a connection between an end-point of a connector and a shape connection point. + """ + + id = RequiredAttribute("id", ST_DrawingElementId) + idx = RequiredAttribute("idx", XsdUnsignedInt) + + +class CT_Connector(BaseShapeElement): + """A line/connector shape `p:cxnSp` element""" + + _tag_seq = ("p:nvCxnSpPr", "p:spPr", "p:style", "p:extLst") + nvCxnSpPr = OneAndOnlyOne("p:nvCxnSpPr") + spPr: CT_ShapeProperties = OneAndOnlyOne("p:spPr") # pyright: ignore[reportAssignmentType] + del _tag_seq + + @classmethod + def new_cxnSp( + cls, + id_: int, + name: str, + prst: str, + x: int, + y: int, + cx: int, + cy: int, + flipH: bool, + flipV: bool, + ) -> CT_Connector: + """Return a new `p:cxnSp` element tree configured as a base connector.""" + flip = (' flipH="1"' if flipH else "") + (' flipV="1"' if flipV else "") + return cast( + CT_Connector, + parse_xml( + f"<p:cxnSp {nsdecls('a', 'p')}>\n" + f" <p:nvCxnSpPr>\n" + f' <p:cNvPr id="{id_}" name="{name}"/>\n' + f" <p:cNvCxnSpPr/>\n" + f" <p:nvPr/>\n" + f" </p:nvCxnSpPr>\n" + f" <p:spPr>\n" + f" <a:xfrm{flip}>\n" + f' <a:off x="{x}" y="{y}"/>\n' + f' <a:ext cx="{cx}" cy="{cy}"/>\n' + f" </a:xfrm>\n" + f' <a:prstGeom prst="{prst}">\n' + f" <a:avLst/>\n" + f" </a:prstGeom>\n" + f" </p:spPr>\n" + f" <p:style>\n" + f' <a:lnRef idx="2">\n' + f' <a:schemeClr val="accent1"/>\n' + f" </a:lnRef>\n" + f' <a:fillRef idx="0">\n' + f' <a:schemeClr val="accent1"/>\n' + f" </a:fillRef>\n" + f' <a:effectRef idx="1">\n' + f' <a:schemeClr val="accent1"/>\n' + f" </a:effectRef>\n" + f' <a:fontRef idx="minor">\n' + f' <a:schemeClr val="tx1"/>\n' + f" </a:fontRef>\n" + f" </p:style>\n" + f"</p:cxnSp>" + ), + ) + + +class CT_ConnectorNonVisual(BaseOxmlElement): + """ + `p:nvCxnSpPr` element, container for the non-visual properties of + a connector, such as name, id, etc. + """ + + cNvPr = OneAndOnlyOne("p:cNvPr") + cNvCxnSpPr = OneAndOnlyOne("p:cNvCxnSpPr") + nvPr = OneAndOnlyOne("p:nvPr") + + +class CT_NonVisualConnectorProperties(BaseOxmlElement): + """ + `p:cNvCxnSpPr` element, container for the non-visual properties specific + to a connector shape, such as connections and connector locking. + """ + + _tag_seq = ("a:cxnSpLocks", "a:stCxn", "a:endCxn", "a:extLst") + stCxn = ZeroOrOne("a:stCxn", successors=_tag_seq[2:]) + endCxn = ZeroOrOne("a:endCxn", successors=_tag_seq[3:]) + del _tag_seq diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/graphfrm.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/graphfrm.py new file mode 100644 index 00000000..efa0b363 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/graphfrm.py @@ -0,0 +1,342 @@ +"""lxml custom element class for CT_GraphicalObjectFrame XML element.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from pptx.oxml import parse_xml +from pptx.oxml.chart.chart import CT_Chart +from pptx.oxml.ns import nsdecls +from pptx.oxml.shapes.shared import BaseShapeElement +from pptx.oxml.simpletypes import XsdBoolean, XsdString +from pptx.oxml.table import CT_Table +from pptx.oxml.xmlchemy import ( + BaseOxmlElement, + OneAndOnlyOne, + OptionalAttribute, + RequiredAttribute, + ZeroOrOne, +) +from pptx.spec import ( + GRAPHIC_DATA_URI_CHART, + GRAPHIC_DATA_URI_OLEOBJ, + GRAPHIC_DATA_URI_TABLE, +) + +if TYPE_CHECKING: + from pptx.oxml.shapes.shared import ( + CT_ApplicationNonVisualDrawingProps, + CT_NonVisualDrawingProps, + CT_Transform2D, + ) + + +class CT_GraphicalObject(BaseOxmlElement): + """`a:graphic` element. + + The container for the reference to or definition of the framed graphical object (table, chart, + etc.). + """ + + graphicData: CT_GraphicalObjectData = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "a:graphicData" + ) + + @property + def chart(self) -> CT_Chart | None: + """The `c:chart` grandchild element, or |None| if not present.""" + return self.graphicData.chart + + +class CT_GraphicalObjectData(BaseShapeElement): + """`p:graphicData` element. + + The direct container for a table, a chart, or another graphical object. + """ + + chart: CT_Chart | None = ZeroOrOne("c:chart") # pyright: ignore[reportAssignmentType] + tbl: CT_Table | None = ZeroOrOne("a:tbl") # pyright: ignore[reportAssignmentType] + uri: str = RequiredAttribute("uri", XsdString) # pyright: ignore[reportAssignmentType] + + @property + def blob_rId(self) -> str | None: + """Optional `r:id` attribute value of `p:oleObj` descendent element. + + This value is `None` when this `p:graphicData` element does not enclose an OLE object. + This value could also be `None` if an enclosed OLE object does not specify this attribute + (it is specified optional in the schema) but so far, all OLE objects we've encountered + specify this value. + """ + return None if self._oleObj is None else self._oleObj.rId + + @property + def is_embedded_ole_obj(self) -> bool | None: + """Optional boolean indicating an embedded OLE object. + + Returns `None` when this `p:graphicData` element does not enclose an OLE object. `True` + indicates an embedded OLE object and `False` indicates a linked OLE object. + """ + return None if self._oleObj is None else self._oleObj.is_embedded + + @property + def progId(self) -> str | None: + """Optional str value of "progId" attribute of `p:oleObj` descendent. + + This value identifies the "type" of the embedded object in terms of the application used + to open it. + + This value is `None` when this `p:graphicData` element does not enclose an OLE object. + This could also be `None` if an enclosed OLE object does not specify this attribute (it is + specified optional in the schema) but so far, all OLE objects we've encountered specify + this value. + """ + return None if self._oleObj is None else self._oleObj.progId + + @property + def showAsIcon(self) -> bool | None: + """Optional value of "showAsIcon" attribute value of `p:oleObj` descendent. + + This value is `None` when this `p:graphicData` element does not enclose an OLE object. It + is False when the `showAsIcon` attribute is omitted on the `p:oleObj` element. + """ + return None if self._oleObj is None else self._oleObj.showAsIcon + + @property + def _oleObj(self) -> CT_OleObject | None: + """Optional `p:oleObj` element contained in this `p:graphicData' element. + + Returns `None` when this graphic-data element does not enclose an OLE object. Note that + this returns the last `p:oleObj` element found. There can be more than one `p:oleObj` + element because an `mc.AlternateContent` element may appear as the child of + `p:graphicData` and that alternate-content subtree can contain multiple compatibility + choices. The last one should suit best for reading purposes because it contains the lowest + common denominator. + """ + oleObjs = cast("list[CT_OleObject]", self.xpath(".//p:oleObj")) + return oleObjs[-1] if oleObjs else None + + +class CT_GraphicalObjectFrame(BaseShapeElement): + """`p:graphicFrame` element. + + A container for a table, a chart, or another graphical object. + """ + + nvGraphicFramePr: CT_GraphicalObjectFrameNonVisual = ( # pyright: ignore[reportAssignmentType] + OneAndOnlyOne("p:nvGraphicFramePr") + ) + xfrm: CT_Transform2D = OneAndOnlyOne("p:xfrm") # pyright: ignore + graphic: CT_GraphicalObject = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "a:graphic" + ) + + @property + def chart(self) -> CT_Chart | None: + """The `c:chart` great-grandchild element, or |None| if not present.""" + return self.graphic.chart + + @property + def chart_rId(self) -> str | None: + """The `rId` attribute of the `c:chart` great-grandchild element. + + |None| if not present. + """ + chart = self.chart + if chart is None: + return None + return chart.rId + + def get_or_add_xfrm(self) -> CT_Transform2D: + """Return the required `p:xfrm` child element. + + Overrides version on BaseShapeElement. + """ + return self.xfrm + + @property + def graphicData(self) -> CT_GraphicalObjectData: + """`a:graphicData` grandchild of this graphic-frame element.""" + return self.graphic.graphicData + + @property + def graphicData_uri(self) -> str: + """str value of `uri` attribute of `a:graphicData` grandchild.""" + return self.graphic.graphicData.uri + + @property + def has_oleobj(self) -> bool: + """`True` for graphicFrame containing an OLE object, `False` otherwise.""" + return self.graphicData.uri == GRAPHIC_DATA_URI_OLEOBJ + + @property + def is_embedded_ole_obj(self) -> bool | None: + """Optional boolean indicating an embedded OLE object. + + Returns `None` when this `p:graphicFrame` element does not enclose an OLE object. `True` + indicates an embedded OLE object and `False` indicates a linked OLE object. + """ + return self.graphicData.is_embedded_ole_obj + + @classmethod + def new_chart_graphicFrame( + cls, id_: int, name: str, rId: str, x: int, y: int, cx: int, cy: int + ) -> CT_GraphicalObjectFrame: + """Return a `p:graphicFrame` element tree populated with a chart element.""" + graphicFrame = CT_GraphicalObjectFrame.new_graphicFrame(id_, name, x, y, cx, cy) + graphicData = graphicFrame.graphic.graphicData + graphicData.uri = GRAPHIC_DATA_URI_CHART + graphicData.append(CT_Chart.new_chart(rId)) + return graphicFrame + + @classmethod + def new_graphicFrame( + cls, id_: int, name: str, x: int, y: int, cx: int, cy: int + ) -> CT_GraphicalObjectFrame: + """Return a new `p:graphicFrame` element tree suitable for containing a table or chart. + + Note that a graphicFrame element is not a valid shape until it contains a graphical object + such as a table. + """ + return cast( + CT_GraphicalObjectFrame, + parse_xml( + f"<p:graphicFrame {nsdecls('a', 'p')}>\n" + f" <p:nvGraphicFramePr>\n" + f' <p:cNvPr id="{id_}" name="{name}"/>\n' + f" <p:cNvGraphicFramePr>\n" + f' <a:graphicFrameLocks noGrp="1"/>\n' + f" </p:cNvGraphicFramePr>\n" + f" <p:nvPr/>\n" + f" </p:nvGraphicFramePr>\n" + f" <p:xfrm>\n" + f' <a:off x="{x}" y="{y}"/>\n' + f' <a:ext cx="{cx}" cy="{cy}"/>\n' + f" </p:xfrm>\n" + f" <a:graphic>\n" + f" <a:graphicData/>\n" + f" </a:graphic>\n" + f"</p:graphicFrame>" + ), + ) + + @classmethod + def new_ole_object_graphicFrame( + cls, + id_: int, + name: str, + ole_object_rId: str, + progId: str, + icon_rId: str, + x: int, + y: int, + cx: int, + cy: int, + imgW: int, + imgH: int, + ) -> CT_GraphicalObjectFrame: + """Return newly-created `p:graphicFrame` for embedded OLE-object. + + `ole_object_rId` identifies the relationship to the OLE-object part. + + `progId` is a str identifying the object-type in terms of the application (program) used + to open it. This becomes an attribute of the same name in the `p:oleObj` element. + + `icon_rId` identifies the relationship to an image part used to display the OLE-object as + an icon (vs. a preview). + """ + return cast( + CT_GraphicalObjectFrame, + parse_xml( + f"<p:graphicFrame {nsdecls('a', 'p', 'r')}>\n" + f" <p:nvGraphicFramePr>\n" + f' <p:cNvPr id="{id_}" name="{name}"/>\n' + f" <p:cNvGraphicFramePr>\n" + f' <a:graphicFrameLocks noGrp="1"/>\n' + f" </p:cNvGraphicFramePr>\n" + f" <p:nvPr/>\n" + f" </p:nvGraphicFramePr>\n" + f" <p:xfrm>\n" + f' <a:off x="{x}" y="{y}"/>\n' + f' <a:ext cx="{cx}" cy="{cy}"/>\n' + f" </p:xfrm>\n" + f" <a:graphic>\n" + f" <a:graphicData" + f' uri="http://schemas.openxmlformats.org/presentationml/2006/ole">\n' + f' <p:oleObj showAsIcon="1"' + f' r:id="{ole_object_rId}"' + f' imgW="{imgW}"' + f' imgH="{imgH}"' + f' progId="{progId}">\n' + f" <p:embed/>\n" + f" <p:pic>\n" + f" <p:nvPicPr>\n" + f' <p:cNvPr id="0" name=""/>\n' + f" <p:cNvPicPr/>\n" + f" <p:nvPr/>\n" + f" </p:nvPicPr>\n" + f" <p:blipFill>\n" + f' <a:blip r:embed="{icon_rId}"/>\n' + f" <a:stretch>\n" + f" <a:fillRect/>\n" + f" </a:stretch>\n" + f" </p:blipFill>\n" + f" <p:spPr>\n" + f" <a:xfrm>\n" + f' <a:off x="{x}" y="{y}"/>\n' + f' <a:ext cx="{cx}" cy="{cy}"/>\n' + f" </a:xfrm>\n" + f' <a:prstGeom prst="rect">\n' + f" <a:avLst/>\n" + f" </a:prstGeom>\n" + f" </p:spPr>\n" + f" </p:pic>\n" + f" </p:oleObj>\n" + f" </a:graphicData>\n" + f" </a:graphic>\n" + f"</p:graphicFrame>" + ), + ) + + @classmethod + def new_table_graphicFrame( + cls, id_: int, name: str, rows: int, cols: int, x: int, y: int, cx: int, cy: int + ) -> CT_GraphicalObjectFrame: + """Return a `p:graphicFrame` element tree populated with a table element.""" + graphicFrame = cls.new_graphicFrame(id_, name, x, y, cx, cy) + graphicFrame.graphic.graphicData.uri = GRAPHIC_DATA_URI_TABLE + graphicFrame.graphic.graphicData.append(CT_Table.new_tbl(rows, cols, cx, cy)) + return graphicFrame + + +class CT_GraphicalObjectFrameNonVisual(BaseOxmlElement): + """`p:nvGraphicFramePr` element. + + This contains the non-visual properties of a graphic frame, such as name, id, etc. + """ + + cNvPr: CT_NonVisualDrawingProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "p:cNvPr" + ) + nvPr: CT_ApplicationNonVisualDrawingProps = ( # pyright: ignore[reportAssignmentType] + OneAndOnlyOne("p:nvPr") + ) + + +class CT_OleObject(BaseOxmlElement): + """`p:oleObj` element, container for an OLE object (e.g. Excel file). + + An OLE object can be either linked or embedded (hence the name). + """ + + progId: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "progId", XsdString + ) + rId: str | None = OptionalAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType] + showAsIcon: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "showAsIcon", XsdBoolean, default=False + ) + + @property + def is_embedded(self) -> bool: + """True when this OLE object is embedded, False when it is linked.""" + return len(self.xpath("./p:embed")) > 0 diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/groupshape.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/groupshape.py new file mode 100644 index 00000000..f62bc666 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/groupshape.py @@ -0,0 +1,280 @@ +"""lxml custom element classes for shape-tree-related XML elements.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Iterator + +from pptx.enum.shapes import MSO_CONNECTOR_TYPE +from pptx.oxml import parse_xml +from pptx.oxml.ns import nsdecls, qn +from pptx.oxml.shapes.autoshape import CT_Shape +from pptx.oxml.shapes.connector import CT_Connector +from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame +from pptx.oxml.shapes.picture import CT_Picture +from pptx.oxml.shapes.shared import BaseShapeElement +from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne, ZeroOrOne +from pptx.util import Emu + +if TYPE_CHECKING: + from pptx.enum.shapes import PP_PLACEHOLDER + from pptx.oxml.shapes import ShapeElement + from pptx.oxml.shapes.shared import CT_Transform2D + + +class CT_GroupShape(BaseShapeElement): + """Used for shape tree (`p:spTree`) as well as the group shape (`p:grpSp`) elements.""" + + nvGrpSpPr: CT_GroupShapeNonVisual = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "p:nvGrpSpPr" + ) + grpSpPr: CT_GroupShapeProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "p:grpSpPr" + ) + + _shape_tags = ( + qn("p:sp"), + qn("p:grpSp"), + qn("p:graphicFrame"), + qn("p:cxnSp"), + qn("p:pic"), + qn("p:contentPart"), + ) + + def add_autoshape( + self, id_: int, name: str, prst: str, x: int, y: int, cx: int, cy: int + ) -> CT_Shape: + """Return new `p:sp` appended to the group/shapetree with specified attributes.""" + sp = CT_Shape.new_autoshape_sp(id_, name, prst, x, y, cx, cy) + self.insert_element_before(sp, "p:extLst") + return sp + + def add_cxnSp( + self, + id_: int, + name: str, + type_member: MSO_CONNECTOR_TYPE, + x: int, + y: int, + cx: int, + cy: int, + flipH: bool, + flipV: bool, + ) -> CT_Connector: + """Return new `p:cxnSp` appended to the group/shapetree with the specified attribues.""" + prst = MSO_CONNECTOR_TYPE.to_xml(type_member) + cxnSp = CT_Connector.new_cxnSp(id_, name, prst, x, y, cx, cy, flipH, flipV) + self.insert_element_before(cxnSp, "p:extLst") + return cxnSp + + def add_freeform_sp(self, x: int, y: int, cx: int, cy: int) -> CT_Shape: + """Append a new freeform `p:sp` with specified position and size.""" + shape_id = self._next_shape_id + name = "Freeform %d" % (shape_id - 1,) + sp = CT_Shape.new_freeform_sp(shape_id, name, x, y, cx, cy) + self.insert_element_before(sp, "p:extLst") + return sp + + def add_grpSp(self) -> CT_GroupShape: + """Return `p:grpSp` element newly appended to this shape tree. + + The element contains no sub-shapes, is positioned at (0, 0), and has + width and height of zero. + """ + shape_id = self._next_shape_id + name = "Group %d" % (shape_id - 1,) + grpSp = CT_GroupShape.new_grpSp(shape_id, name) + self.insert_element_before(grpSp, "p:extLst") + return grpSp + + def add_pic( + self, id_: int, name: str, desc: str, rId: str, x: int, y: int, cx: int, cy: int + ) -> CT_Picture: + """Append a `p:pic` shape to the group/shapetree having properties as specified in call.""" + pic = CT_Picture.new_pic(id_, name, desc, rId, x, y, cx, cy) + self.insert_element_before(pic, "p:extLst") + return pic + + def add_placeholder( + self, id_: int, name: str, ph_type: PP_PLACEHOLDER, orient: str, sz: str, idx: int + ) -> CT_Shape: + """Append a newly-created placeholder `p:sp` shape having the specified properties.""" + sp = CT_Shape.new_placeholder_sp(id_, name, ph_type, orient, sz, idx) + self.insert_element_before(sp, "p:extLst") + return sp + + def add_table( + self, id_: int, name: str, rows: int, cols: int, x: int, y: int, cx: int, cy: int + ) -> CT_GraphicalObjectFrame: + """Append a `p:graphicFrame` shape containing a table as specified in call.""" + graphicFrame = CT_GraphicalObjectFrame.new_table_graphicFrame( + id_, name, rows, cols, x, y, cx, cy + ) + self.insert_element_before(graphicFrame, "p:extLst") + return graphicFrame + + def add_textbox(self, id_: int, name: str, x: int, y: int, cx: int, cy: int) -> CT_Shape: + """Append a newly-created textbox `p:sp` shape having the specified position and size.""" + sp = CT_Shape.new_textbox_sp(id_, name, x, y, cx, cy) + self.insert_element_before(sp, "p:extLst") + return sp + + @property + def chExt(self): + """Descendent `p:grpSpPr/a:xfrm/a:chExt` element.""" + return self.grpSpPr.get_or_add_xfrm().get_or_add_chExt() + + @property + def chOff(self): + """Descendent `p:grpSpPr/a:xfrm/a:chOff` element.""" + return self.grpSpPr.get_or_add_xfrm().get_or_add_chOff() + + def get_or_add_xfrm(self) -> CT_Transform2D: + """Return the `a:xfrm` grandchild element, newly-added if not present.""" + return self.grpSpPr.get_or_add_xfrm() + + def iter_ph_elms(self): + """Generate each placeholder shape child element in document order.""" + for e in self.iter_shape_elms(): + if e.has_ph_elm: + yield e + + def iter_shape_elms(self) -> Iterator[ShapeElement]: + """Generate each child of this `p:spTree` element that corresponds to a shape. + + Items appear in XML document order. + """ + for elm in self.iterchildren(): + if elm.tag in self._shape_tags: + yield elm + + @property + def max_shape_id(self) -> int: + """Maximum int value assigned as @id in this slide. + + This is generally a shape-id, but ids can be assigned to other + objects so we just check all @id values anywhere in the document + (XML id-values have document scope). + + In practice, its minimum value is 1 because the spTree element itself + is always assigned id="1". + """ + id_str_lst = self.xpath("//@id") + used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()] + return max(used_ids) if used_ids else 0 + + @classmethod + def new_grpSp(cls, id_: int, name: str) -> CT_GroupShape: + """Return new "loose" `p:grpSp` element having `id_` and `name`.""" + xml = ( + "<p:grpSp %s>\n" + " <p:nvGrpSpPr>\n" + ' <p:cNvPr id="%%d" name="%%s"/>\n' + " <p:cNvGrpSpPr/>\n" + " <p:nvPr/>\n" + " </p:nvGrpSpPr>\n" + " <p:grpSpPr>\n" + " <a:xfrm>\n" + ' <a:off x="0" y="0"/>\n' + ' <a:ext cx="0" cy="0"/>\n' + ' <a:chOff x="0" y="0"/>\n' + ' <a:chExt cx="0" cy="0"/>\n' + " </a:xfrm>\n" + " </p:grpSpPr>\n" + "</p:grpSp>" % nsdecls("a", "p", "r") + ) % (id_, name) + grpSp = parse_xml(xml) + return grpSp + + def recalculate_extents(self) -> None: + """Adjust x, y, cx, and cy to incorporate all contained shapes. + + This would typically be called when a contained shape is added, + removed, or its position or size updated. + + This method is recursive "upwards" since a change in a group shape + can change the position and size of its containing group. + """ + if not self.tag == qn("p:grpSp"): + return + + x, y, cx, cy = self._child_extents + + self.chOff.x = self.x = x + self.chOff.y = self.y = y + self.chExt.cx = self.cx = cx + self.chExt.cy = self.cy = cy + self.getparent().recalculate_extents() + + @property + def xfrm(self) -> CT_Transform2D | None: + """The `a:xfrm` grandchild element or |None| if not found.""" + return self.grpSpPr.xfrm + + @property + def _child_extents(self) -> tuple[int, int, int, int]: + """(x, y, cx, cy) tuple representing net position and size. + + The values are formed as a composite of the contained child shapes. + """ + child_shape_elms = list(self.iter_shape_elms()) + + if not child_shape_elms: + return Emu(0), Emu(0), Emu(0), Emu(0) + + min_x = min([xSp.x for xSp in child_shape_elms]) + min_y = min([xSp.y for xSp in child_shape_elms]) + max_x = max([(xSp.x + xSp.cx) for xSp in child_shape_elms]) + max_y = max([(xSp.y + xSp.cy) for xSp in child_shape_elms]) + + x = min_x + y = min_y + cx = max_x - min_x + cy = max_y - min_y + + return x, y, cx, cy + + @property + def _next_shape_id(self) -> int: + """Return unique shape id suitable for use with a new shape element. + + The returned id is the next available positive integer drawing object + id in shape tree, starting from 1 and making use of any gaps in + numbering. In practice, the minimum id is 2 because the spTree + element itself is always assigned id="1". + """ + id_str_lst = self.xpath("//@id") + used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()] + for n in range(1, len(used_ids) + 2): + if n not in used_ids: + return n + + +class CT_GroupShapeNonVisual(BaseShapeElement): + """`p:nvGrpSpPr` element.""" + + cNvPr = OneAndOnlyOne("p:cNvPr") + + +class CT_GroupShapeProperties(BaseOxmlElement): + """p:grpSpPr element""" + + get_or_add_xfrm: Callable[[], CT_Transform2D] + + _tag_seq = ( + "a:xfrm", + "a:noFill", + "a:solidFill", + "a:gradFill", + "a:blipFill", + "a:pattFill", + "a:grpFill", + "a:effectLst", + "a:effectDag", + "a:scene3d", + "a:extLst", + ) + xfrm: CT_Transform2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:xfrm", successors=_tag_seq[1:] + ) + effectLst = ZeroOrOne("a:effectLst", successors=_tag_seq[8:]) + del _tag_seq diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/picture.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/picture.py new file mode 100644 index 00000000..bacc9719 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/picture.py @@ -0,0 +1,270 @@ +"""lxml custom element classes for picture-related XML elements.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast +from xml.sax.saxutils import escape + +from pptx.oxml import parse_xml +from pptx.oxml.ns import nsdecls +from pptx.oxml.shapes.shared import BaseShapeElement +from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne + +if TYPE_CHECKING: + from pptx.oxml.shapes.shared import CT_ShapeProperties + from pptx.util import Length + + +class CT_Picture(BaseShapeElement): + """`p:pic` element. + + Represents a picture shape (an image placement on a slide). + """ + + nvPicPr = OneAndOnlyOne("p:nvPicPr") + blipFill = OneAndOnlyOne("p:blipFill") + spPr: CT_ShapeProperties = OneAndOnlyOne("p:spPr") # pyright: ignore[reportAssignmentType] + + @property + def blip_rId(self) -> str | None: + """Value of `p:blipFill/a:blip/@r:embed`. + + Returns |None| if not present. + """ + blip = self.blipFill.blip + if blip is not None and blip.rEmbed is not None: + return blip.rEmbed + return None + + def crop_to_fit(self, image_size, view_size): + """ + Set cropping values in `p:blipFill/a:srcRect` such that an image of + *image_size* will stretch to exactly fit *view_size* when its aspect + ratio is preserved. + """ + self.blipFill.crop(self._fill_cropping(image_size, view_size)) + + def get_or_add_ln(self): + """ + Return the <a:ln> grandchild element, newly added if not present. + """ + return self.spPr.get_or_add_ln() + + @property + def ln(self): + """ + ``<a:ln>`` grand-child element or |None| if not present + """ + return self.spPr.ln + + @classmethod + def new_ph_pic(cls, id_, name, desc, rId): + """ + Return a new `p:pic` placeholder element populated with the supplied + parameters. + """ + return parse_xml(cls._pic_ph_tmpl() % (id_, name, desc, rId)) + + @classmethod + def new_pic(cls, shape_id, name, desc, rId, x, y, cx, cy): + """Return new `<p:pic>` element tree configured with supplied parameters.""" + return parse_xml(cls._pic_tmpl() % (shape_id, name, escape(desc), rId, x, y, cx, cy)) + + @classmethod + def new_video_pic( + cls, + shape_id: int, + shape_name: str, + video_rId: str, + media_rId: str, + poster_frame_rId: str, + x: Length, + y: Length, + cx: Length, + cy: Length, + ) -> CT_Picture: + """Return a new `p:pic` populated with the specified video.""" + return cast( + CT_Picture, + parse_xml( + cls._pic_video_tmpl() + % ( + shape_id, + shape_name, + video_rId, + media_rId, + poster_frame_rId, + x, + y, + cx, + cy, + ) + ), + ) + + @property + def srcRect_b(self): + """Value of `p:blipFill/a:srcRect/@b` or 0.0 if not present.""" + return self._srcRect_x("b") + + @srcRect_b.setter + def srcRect_b(self, value): + self.blipFill.get_or_add_srcRect().b = value + + @property + def srcRect_l(self): + """Value of `p:blipFill/a:srcRect/@l` or 0.0 if not present.""" + return self._srcRect_x("l") + + @srcRect_l.setter + def srcRect_l(self, value): + self.blipFill.get_or_add_srcRect().l = value # noqa + + @property + def srcRect_r(self): + """Value of `p:blipFill/a:srcRect/@r` or 0.0 if not present.""" + return self._srcRect_x("r") + + @srcRect_r.setter + def srcRect_r(self, value): + self.blipFill.get_or_add_srcRect().r = value + + @property + def srcRect_t(self): + """Value of `p:blipFill/a:srcRect/@t` or 0.0 if not present.""" + return self._srcRect_x("t") + + @srcRect_t.setter + def srcRect_t(self, value): + self.blipFill.get_or_add_srcRect().t = value + + def _fill_cropping(self, image_size, view_size): + """ + Return a (left, top, right, bottom) 4-tuple containing the cropping + values required to display an image of *image_size* in *view_size* + when stretched proportionately. Each value is a percentage expressed + as a fraction of 1.0, e.g. 0.425 represents 42.5%. *image_size* and + *view_size* are each (width, height) pairs. + """ + + def aspect_ratio(width, height): + return width / height + + ar_view = aspect_ratio(*view_size) + ar_image = aspect_ratio(*image_size) + + if ar_view < ar_image: # image too wide + crop = (1.0 - (ar_view / ar_image)) / 2.0 + return (crop, 0.0, crop, 0.0) + if ar_view > ar_image: # image too tall + crop = (1.0 - (ar_image / ar_view)) / 2.0 + return (0.0, crop, 0.0, crop) + return (0.0, 0.0, 0.0, 0.0) + + @classmethod + def _pic_ph_tmpl(cls): + return ( + "<p:pic %s>\n" + " <p:nvPicPr>\n" + ' <p:cNvPr id="%%d" name="%%s" descr="%%s"/>\n' + " <p:cNvPicPr>\n" + ' <a:picLocks noGrp="1" noChangeAspect="1"/>\n' + " </p:cNvPicPr>\n" + " <p:nvPr/>\n" + " </p:nvPicPr>\n" + " <p:blipFill>\n" + ' <a:blip r:embed="%%s"/>\n' + " <a:stretch>\n" + " <a:fillRect/>\n" + " </a:stretch>\n" + " </p:blipFill>\n" + " <p:spPr/>\n" + "</p:pic>" % nsdecls("p", "a", "r") + ) + + @classmethod + def _pic_tmpl(cls): + return ( + "<p:pic %s>\n" + " <p:nvPicPr>\n" + ' <p:cNvPr id="%%d" name="%%s" descr="%%s"/>\n' + " <p:cNvPicPr>\n" + ' <a:picLocks noChangeAspect="1"/>\n' + " </p:cNvPicPr>\n" + " <p:nvPr/>\n" + " </p:nvPicPr>\n" + " <p:blipFill>\n" + ' <a:blip r:embed="%%s"/>\n' + " <a:stretch>\n" + " <a:fillRect/>\n" + " </a:stretch>\n" + " </p:blipFill>\n" + " <p:spPr>\n" + " <a:xfrm>\n" + ' <a:off x="%%d" y="%%d"/>\n' + ' <a:ext cx="%%d" cy="%%d"/>\n' + " </a:xfrm>\n" + ' <a:prstGeom prst="rect">\n' + " <a:avLst/>\n" + " </a:prstGeom>\n" + " </p:spPr>\n" + "</p:pic>" % nsdecls("a", "p", "r") + ) + + @classmethod + def _pic_video_tmpl(cls): + return ( + "<p:pic %s>\n" + " <p:nvPicPr>\n" + ' <p:cNvPr id="%%d" name="%%s">\n' + ' <a:hlinkClick r:id="" action="ppaction://media"/>\n' + " </p:cNvPr>\n" + " <p:cNvPicPr>\n" + ' <a:picLocks noChangeAspect="1"/>\n' + " </p:cNvPicPr>\n" + " <p:nvPr>\n" + ' <a:videoFile r:link="%%s"/>\n' + " <p:extLst>\n" + ' <p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">\n' + ' <p14:media xmlns:p14="http://schemas.microsoft.com/of' + 'fice/powerpoint/2010/main" r:embed="%%s"/>\n' + " </p:ext>\n" + " </p:extLst>\n" + " </p:nvPr>\n" + " </p:nvPicPr>\n" + " <p:blipFill>\n" + ' <a:blip r:embed="%%s"/>\n' + " <a:stretch>\n" + " <a:fillRect/>\n" + " </a:stretch>\n" + " </p:blipFill>\n" + " <p:spPr>\n" + " <a:xfrm>\n" + ' <a:off x="%%d" y="%%d"/>\n' + ' <a:ext cx="%%d" cy="%%d"/>\n' + " </a:xfrm>\n" + ' <a:prstGeom prst="rect">\n' + " <a:avLst/>\n" + " </a:prstGeom>\n" + " </p:spPr>\n" + "</p:pic>" % nsdecls("a", "p", "r") + ) + + def _srcRect_x(self, attr_name): + """ + Value of `p:blipFill/a:srcRect/@{attr_name}` or 0.0 if not present. + """ + srcRect = self.blipFill.srcRect + if srcRect is None: + return 0.0 + return getattr(srcRect, attr_name) + + +class CT_PictureNonVisual(BaseOxmlElement): + """ + ``<p:nvPicPr>`` element, containing non-visual properties for a picture + shape. + """ + + cNvPr = OneAndOnlyOne("p:cNvPr") + nvPr = OneAndOnlyOne("p:nvPr") diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/shared.py b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/shared.py new file mode 100644 index 00000000..d9f94569 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/shapes/shared.py @@ -0,0 +1,523 @@ +"""Common shape-related oxml objects.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from pptx.dml.fill import CT_GradientFillProperties +from pptx.enum.shapes import PP_PLACEHOLDER +from pptx.oxml.ns import qn +from pptx.oxml.simpletypes import ( + ST_Angle, + ST_Coordinate, + ST_Direction, + ST_DrawingElementId, + ST_LineWidth, + ST_PlaceholderSize, + ST_PositiveCoordinate, + XsdBoolean, + XsdString, + XsdUnsignedInt, +) +from pptx.oxml.xmlchemy import ( + BaseOxmlElement, + Choice, + OptionalAttribute, + OxmlElement, + RequiredAttribute, + ZeroOrOne, + ZeroOrOneChoice, +) +from pptx.util import Emu + +if TYPE_CHECKING: + from pptx.oxml.action import CT_Hyperlink + from pptx.oxml.shapes.autoshape import CT_CustomGeometry2D, CT_PresetGeometry2D + from pptx.util import Length + + +class BaseShapeElement(BaseOxmlElement): + """Provides common behavior for shape element classes like CT_Shape, CT_Picture, etc.""" + + spPr: CT_ShapeProperties + + @property + def cx(self) -> Length: + return self._get_xfrm_attr("cx") + + @cx.setter + def cx(self, value): + self._set_xfrm_attr("cx", value) + + @property + def cy(self) -> Length: + return self._get_xfrm_attr("cy") + + @cy.setter + def cy(self, value): + self._set_xfrm_attr("cy", value) + + @property + def flipH(self): + return bool(self._get_xfrm_attr("flipH")) + + @flipH.setter + def flipH(self, value): + self._set_xfrm_attr("flipH", value) + + @property + def flipV(self): + return bool(self._get_xfrm_attr("flipV")) + + @flipV.setter + def flipV(self, value): + self._set_xfrm_attr("flipV", value) + + def get_or_add_xfrm(self): + """Return the `a:xfrm` grandchild element, newly-added if not present. + + This version works for `p:sp`, `p:cxnSp`, and `p:pic` elements, others will need to + override. + """ + return self.spPr.get_or_add_xfrm() + + @property + def has_ph_elm(self): + """ + True if this shape element has a `p:ph` descendant, indicating it + is a placeholder shape. False otherwise. + """ + return self.ph is not None + + @property + def ph(self) -> CT_Placeholder | None: + """The `p:ph` descendant element if there is one, None otherwise.""" + ph_elms = self.xpath("./*[1]/p:nvPr/p:ph") + if len(ph_elms) == 0: + return None + return ph_elms[0] + + @property + def ph_idx(self) -> int: + """Integer value of placeholder idx attribute. + + Raises |ValueError| if shape is not a placeholder. + """ + ph = self.ph + if ph is None: + raise ValueError("not a placeholder shape") + return ph.idx + + @property + def ph_orient(self) -> str: + """Placeholder orientation, e.g. 'vert'. + + Raises |ValueError| if shape is not a placeholder. + """ + ph = self.ph + if ph is None: + raise ValueError("not a placeholder shape") + return ph.orient + + @property + def ph_sz(self) -> str: + """Placeholder size, e.g. ST_PlaceholderSize.HALF. + + Raises `ValueError` if shape is not a placeholder. + """ + ph = self.ph + if ph is None: + raise ValueError("not a placeholder shape") + return ph.sz + + @property + def ph_type(self): + """Placeholder type, e.g. ST_PlaceholderType.TITLE ('title'). + + Raises `ValueError` if shape is not a placeholder. + """ + ph = self.ph + if ph is None: + raise ValueError("not a placeholder shape") + return ph.type + + @property + def rot(self) -> float: + """Float representing degrees this shape is rotated clockwise.""" + xfrm = self.xfrm + if xfrm is None or xfrm.rot is None: + return 0.0 + return xfrm.rot + + @rot.setter + def rot(self, value: float): + self.get_or_add_xfrm().rot = value + + @property + def shape_id(self): + """ + Integer id of this shape + """ + return self._nvXxPr.cNvPr.id + + @property + def shape_name(self): + """ + Name of this shape + """ + return self._nvXxPr.cNvPr.name + + @property + def txBody(self): + """Child `p:txBody` element, None if not present.""" + return self.find(qn("p:txBody")) + + @property + def x(self) -> Length: + return self._get_xfrm_attr("x") + + @x.setter + def x(self, value): + self._set_xfrm_attr("x", value) + + @property + def xfrm(self): + """The `a:xfrm` grandchild element or |None| if not found. + + This version works for `p:sp`, `p:cxnSp`, and `p:pic` elements, others will need to + override. + """ + return self.spPr.xfrm + + @property + def y(self) -> Length: + return self._get_xfrm_attr("y") + + @y.setter + def y(self, value): + self._set_xfrm_attr("y", value) + + @property + def _nvXxPr(self): + """ + Required non-visual shape properties element for this shape. Actual + name depends on the shape type, e.g. `p:nvPicPr` for picture + shape. + """ + return self.xpath("./*[1]")[0] + + def _get_xfrm_attr(self, name: str) -> Length | None: + xfrm = self.xfrm + if xfrm is None: + return None + return getattr(xfrm, name) + + def _set_xfrm_attr(self, name, value): + xfrm = self.get_or_add_xfrm() + setattr(xfrm, name, value) + + +class CT_ApplicationNonVisualDrawingProps(BaseOxmlElement): + """`p:nvPr` element.""" + + get_or_add_ph: Callable[[], CT_Placeholder] + + ph = ZeroOrOne( + "p:ph", + successors=( + "a:audioCd", + "a:wavAudioFile", + "a:audioFile", + "a:videoFile", + "a:quickTimeFile", + "p:custDataLst", + "p:extLst", + ), + ) + + +class CT_LineProperties(BaseOxmlElement): + """Custom element class for <a:ln> element""" + + _tag_seq = ( + "a:noFill", + "a:solidFill", + "a:gradFill", + "a:pattFill", + "a:prstDash", + "a:custDash", + "a:round", + "a:bevel", + "a:miter", + "a:headEnd", + "a:tailEnd", + "a:extLst", + ) + eg_lineFillProperties = ZeroOrOneChoice( + ( + Choice("a:noFill"), + Choice("a:solidFill"), + Choice("a:gradFill"), + Choice("a:pattFill"), + ), + successors=_tag_seq[4:], + ) + prstDash = ZeroOrOne("a:prstDash", successors=_tag_seq[5:]) + custDash = ZeroOrOne("a:custDash", successors=_tag_seq[6:]) + del _tag_seq + w = OptionalAttribute("w", ST_LineWidth, default=Emu(0)) + + @property + def eg_fillProperties(self): + """ + Required to fulfill the interface used by dml.fill. + """ + return self.eg_lineFillProperties + + @property + def prstDash_val(self): + """Return value of `val` attribute of `a:prstDash` child. + + Return |None| if not present. + """ + prstDash = self.prstDash + if prstDash is None: + return None + return prstDash.val + + @prstDash_val.setter + def prstDash_val(self, val): + self._remove_custDash() + prstDash = self.get_or_add_prstDash() + prstDash.val = val + + +class CT_NonVisualDrawingProps(BaseOxmlElement): + """`p:cNvPr` custom element class.""" + + get_or_add_hlinkClick: Callable[[], CT_Hyperlink] + get_or_add_hlinkHover: Callable[[], CT_Hyperlink] + + _tag_seq = ("a:hlinkClick", "a:hlinkHover", "a:extLst") + hlinkClick: CT_Hyperlink | None = ZeroOrOne("a:hlinkClick", successors=_tag_seq[1:]) + hlinkHover: CT_Hyperlink | None = ZeroOrOne("a:hlinkHover", successors=_tag_seq[2:]) + id = RequiredAttribute("id", ST_DrawingElementId) + name = RequiredAttribute("name", XsdString) + del _tag_seq + + +class CT_Placeholder(BaseOxmlElement): + """`p:ph` custom element class.""" + + type: PP_PLACEHOLDER = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "type", PP_PLACEHOLDER, default=PP_PLACEHOLDER.OBJECT + ) + orient: str = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "orient", ST_Direction, default=ST_Direction.HORZ + ) + sz: str = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "sz", ST_PlaceholderSize, default=ST_PlaceholderSize.FULL + ) + idx: int = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "idx", XsdUnsignedInt, default=0 + ) + + +class CT_Point2D(BaseOxmlElement): + """ + Custom element class for <a:off> element. + """ + + x: Length = RequiredAttribute("x", ST_Coordinate) # pyright: ignore[reportAssignmentType] + y: Length = RequiredAttribute("y", ST_Coordinate) # pyright: ignore[reportAssignmentType] + + +class CT_PositiveSize2D(BaseOxmlElement): + """ + Custom element class for <a:ext> element. + """ + + cx = RequiredAttribute("cx", ST_PositiveCoordinate) + cy = RequiredAttribute("cy", ST_PositiveCoordinate) + + +class CT_ShapeProperties(BaseOxmlElement): + """Custom element class for `p:spPr` element. + + Shared by `p:sp`, `p:cxnSp`, and `p:pic` elements as well as a few more obscure ones. + """ + + get_or_add_xfrm: Callable[[], CT_Transform2D] + get_or_add_ln: Callable[[], CT_LineProperties] + _add_prstGeom: Callable[[], CT_PresetGeometry2D] + _remove_custGeom: Callable[[], None] + + _tag_seq = ( + "a:xfrm", + "a:custGeom", + "a:prstGeom", + "a:noFill", + "a:solidFill", + "a:gradFill", + "a:blipFill", + "a:pattFill", + "a:grpFill", + "a:ln", + "a:effectLst", + "a:effectDag", + "a:scene3d", + "a:sp3d", + "a:extLst", + ) + xfrm: CT_Transform2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:xfrm", successors=_tag_seq[1:] + ) + custGeom: CT_CustomGeometry2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:custGeom", successors=_tag_seq[2:] + ) + prstGeom: CT_PresetGeometry2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:prstGeom", successors=_tag_seq[3:] + ) + eg_fillProperties = ZeroOrOneChoice( + ( + Choice("a:noFill"), + Choice("a:solidFill"), + Choice("a:gradFill"), + Choice("a:blipFill"), + Choice("a:pattFill"), + Choice("a:grpFill"), + ), + successors=_tag_seq[9:], + ) + ln: CT_LineProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:ln", successors=_tag_seq[10:] + ) + effectLst = ZeroOrOne("a:effectLst", successors=_tag_seq[11:]) + del _tag_seq + + @property + def cx(self): + """ + Shape width as an instance of Emu, or None if not present. + """ + cx_str_lst = self.xpath("./a:xfrm/a:ext/@cx") + if not cx_str_lst: + return None + return Emu(cx_str_lst[0]) + + @property + def cy(self): + """ + Shape height as an instance of Emu, or None if not present. + """ + cy_str_lst = self.xpath("./a:xfrm/a:ext/@cy") + if not cy_str_lst: + return None + return Emu(cy_str_lst[0]) + + @property + def x(self) -> Length | None: + """Distance between the left edge of the slide and left edge of the shape. + + 0 if not present. + """ + x_str_lst = self.xpath("./a:xfrm/a:off/@x") + if not x_str_lst: + return None + return Emu(x_str_lst[0]) + + @property + def y(self): + """ + The offset of the top of the shape from the top of the slide, as an + instance of Emu. None if not present. + """ + y_str_lst = self.xpath("./a:xfrm/a:off/@y") + if not y_str_lst: + return None + return Emu(y_str_lst[0]) + + def _new_gradFill(self): + return CT_GradientFillProperties.new_gradFill() + + +class CT_Transform2D(BaseOxmlElement): + """`a:xfrm` custom element class. + + NOTE: this is a composite including CT_GroupTransform2D, which appears + with the `a:xfrm` tag in a group shape (including a slide `p:spTree`). + """ + + _tag_seq = ("a:off", "a:ext", "a:chOff", "a:chExt") + off: CT_Point2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:off", successors=_tag_seq[1:] + ) + ext = ZeroOrOne("a:ext", successors=_tag_seq[2:]) + chOff = ZeroOrOne("a:chOff", successors=_tag_seq[3:]) + chExt = ZeroOrOne("a:chExt", successors=_tag_seq[4:]) + del _tag_seq + rot: float | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "rot", ST_Angle, default=0.0 + ) + flipH = OptionalAttribute("flipH", XsdBoolean, default=False) + flipV = OptionalAttribute("flipV", XsdBoolean, default=False) + + @property + def x(self): + off = self.off + if off is None: + return None + return off.x + + @x.setter + def x(self, value): + off = self.get_or_add_off() + off.x = value + + @property + def y(self): + off = self.off + if off is None: + return None + return off.y + + @y.setter + def y(self, value): + off = self.get_or_add_off() + off.y = value + + @property + def cx(self): + ext = self.ext + if ext is None: + return None + return ext.cx + + @cx.setter + def cx(self, value): + ext = self.get_or_add_ext() + ext.cx = value + + @property + def cy(self): + ext = self.ext + if ext is None: + return None + return ext.cy + + @cy.setter + def cy(self, value): + ext = self.get_or_add_ext() + ext.cy = value + + def _new_ext(self): + ext = OxmlElement("a:ext") + ext.cx = 0 + ext.cy = 0 + return ext + + def _new_off(self): + off = OxmlElement("a:off") + off.x = 0 + off.y = 0 + return off |
