about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py')
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py337
1 files changed, 337 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py b/.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py
new file mode 100644
index 00000000..afe87385
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/shapes/freeform.py
@@ -0,0 +1,337 @@
+"""Objects related to construction of freeform shapes."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Iterable, Iterator, Sequence
+
+from pptx.util import Emu, lazyproperty
+
+if TYPE_CHECKING:
+    from typing_extensions import TypeAlias
+
+    from pptx.oxml.shapes.autoshape import (
+        CT_Path2D,
+        CT_Path2DClose,
+        CT_Path2DLineTo,
+        CT_Path2DMoveTo,
+        CT_Shape,
+    )
+    from pptx.shapes.shapetree import _BaseGroupShapes  # pyright: ignore[reportPrivateUsage]
+    from pptx.util import Length
+
+CT_DrawingOperation: TypeAlias = "CT_Path2DClose | CT_Path2DLineTo | CT_Path2DMoveTo"
+DrawingOperation: TypeAlias = "_LineSegment | _MoveTo | _Close"
+
+
+class FreeformBuilder(Sequence[DrawingOperation]):
+    """Allows a freeform shape to be specified and created.
+
+    The initial pen position is provided on construction. From there, drawing proceeds using
+    successive calls to draw line segments. The freeform shape may be closed by calling the
+    :meth:`close` method.
+
+    A shape may have more than one contour, in which case overlapping areas are "subtracted". A
+    contour is a sequence of line segments beginning with a "move-to" operation. A move-to
+    operation is automatically inserted in each new freeform; additional move-to ops can be
+    inserted with the `.move_to()` method.
+    """
+
+    def __init__(
+        self,
+        shapes: _BaseGroupShapes,
+        start_x: Length,
+        start_y: Length,
+        x_scale: float,
+        y_scale: float,
+    ):
+        super(FreeformBuilder, self).__init__()
+        self._shapes = shapes
+        self._start_x = start_x
+        self._start_y = start_y
+        self._x_scale = x_scale
+        self._y_scale = y_scale
+
+    def __getitem__(  # pyright: ignore[reportIncompatibleMethodOverride]
+        self, idx: int
+    ) -> DrawingOperation:
+        return self._drawing_operations.__getitem__(idx)
+
+    def __iter__(self) -> Iterator[DrawingOperation]:
+        return self._drawing_operations.__iter__()
+
+    def __len__(self):
+        return self._drawing_operations.__len__()
+
+    @classmethod
+    def new(
+        cls,
+        shapes: _BaseGroupShapes,
+        start_x: float,
+        start_y: float,
+        x_scale: float,
+        y_scale: float,
+    ):
+        """Return a new |FreeformBuilder| object.
+
+        The initial pen location is specified (in local coordinates) by
+        (`start_x`, `start_y`).
+        """
+        return cls(shapes, Emu(int(round(start_x))), Emu(int(round(start_y))), x_scale, y_scale)
+
+    def add_line_segments(self, vertices: Iterable[tuple[float, float]], close: bool = True):
+        """Add a straight line segment to each point in `vertices`.
+
+        `vertices` must be an iterable of (x, y) pairs (2-tuples). Each x and y value is rounded
+        to the nearest integer before use. The optional `close` parameter determines whether the
+        resulting contour is `closed` or left `open`.
+
+        Returns this |FreeformBuilder| object so it can be used in chained calls.
+        """
+        for x, y in vertices:
+            self._add_line_segment(x, y)
+        if close:
+            self._add_close()
+        return self
+
+    def convert_to_shape(self, origin_x: Length = Emu(0), origin_y: Length = Emu(0)):
+        """Return new freeform shape positioned relative to specified offset.
+
+        `origin_x` and `origin_y` locate the origin of the local coordinate system in slide
+        coordinates (EMU), perhaps most conveniently by use of a |Length| object.
+
+        Note that this method may be called more than once to add multiple shapes of the same
+        geometry in different locations on the slide.
+        """
+        sp = self._add_freeform_sp(origin_x, origin_y)
+        path = self._start_path(sp)
+        for drawing_operation in self:
+            drawing_operation.apply_operation_to(path)
+        return self._shapes._shape_factory(sp)  # pyright: ignore[reportPrivateUsage]
+
+    def move_to(self, x: float, y: float):
+        """Move pen to (x, y) (local coordinates) without drawing line.
+
+        Returns this |FreeformBuilder| object so it can be used in chained calls.
+        """
+        self._drawing_operations.append(_MoveTo.new(self, x, y))
+        return self
+
+    @property
+    def shape_offset_x(self) -> Length:
+        """Return x distance of shape origin from local coordinate origin.
+
+        The returned integer represents the leftmost extent of the freeform shape, in local
+        coordinates. Note that the bounding box of the shape need not start at the local origin.
+        """
+        min_x = self._start_x
+        for drawing_operation in self:
+            if isinstance(drawing_operation, _Close):
+                continue
+            min_x = min(min_x, drawing_operation.x)
+        return Emu(min_x)
+
+    @property
+    def shape_offset_y(self) -> Length:
+        """Return y distance of shape origin from local coordinate origin.
+
+        The returned integer represents the topmost extent of the freeform shape, in local
+        coordinates. Note that the bounding box of the shape need not start at the local origin.
+        """
+        min_y = self._start_y
+        for drawing_operation in self:
+            if isinstance(drawing_operation, _Close):
+                continue
+            min_y = min(min_y, drawing_operation.y)
+        return Emu(min_y)
+
+    def _add_close(self):
+        """Add a close |_Close| operation to the drawing sequence."""
+        self._drawing_operations.append(_Close.new())
+
+    def _add_freeform_sp(self, origin_x: Length, origin_y: Length):
+        """Add a freeform `p:sp` element having no drawing elements.
+
+        `origin_x` and `origin_y` are specified in slide coordinates, and represent the location
+        of the local coordinates origin on the slide.
+        """
+        spTree = self._shapes._spTree  # pyright: ignore[reportPrivateUsage]
+        return spTree.add_freeform_sp(
+            origin_x + self._left, origin_y + self._top, self._width, self._height
+        )
+
+    def _add_line_segment(self, x: float, y: float) -> None:
+        """Add a |_LineSegment| operation to the drawing sequence."""
+        self._drawing_operations.append(_LineSegment.new(self, x, y))
+
+    @lazyproperty
+    def _drawing_operations(self) -> list[DrawingOperation]:
+        """Return the sequence of drawing operation objects for freeform."""
+        return []
+
+    @property
+    def _dx(self) -> Length:
+        """Return width of this shape's path in local units."""
+        min_x = max_x = self._start_x
+        for drawing_operation in self:
+            if isinstance(drawing_operation, _Close):
+                continue
+            min_x = min(min_x, drawing_operation.x)
+            max_x = max(max_x, drawing_operation.x)
+        return Emu(max_x - min_x)
+
+    @property
+    def _dy(self) -> Length:
+        """Return integer height of this shape's path in local units."""
+        min_y = max_y = self._start_y
+        for drawing_operation in self:
+            if isinstance(drawing_operation, _Close):
+                continue
+            min_y = min(min_y, drawing_operation.y)
+            max_y = max(max_y, drawing_operation.y)
+        return Emu(max_y - min_y)
+
+    @property
+    def _height(self):
+        """Return vertical size of this shape's path in slide coordinates.
+
+        This value is based on the actual extents of the shape and does not include any
+        positioning offset.
+        """
+        return int(round(self._dy * self._y_scale))
+
+    @property
+    def _left(self):
+        """Return leftmost extent of this shape's path in slide coordinates.
+
+        Note that this value does not include any positioning offset; it assumes the drawing
+        (local) coordinate origin is at (0, 0) on the slide.
+        """
+        return int(round(self.shape_offset_x * self._x_scale))
+
+    def _local_to_shape(self, local_x: Length, local_y: Length) -> tuple[Length, Length]:
+        """Translate local coordinates point to shape coordinates.
+
+        Shape coordinates have the same unit as local coordinates, but are offset such that the
+        origin of the shape coordinate system (0, 0) is located at the top-left corner of the
+        shape bounding box.
+        """
+        return Emu(local_x - self.shape_offset_x), Emu(local_y - self.shape_offset_y)
+
+    def _start_path(self, sp: CT_Shape) -> CT_Path2D:
+        """Return a newly created `a:path` element added to `sp`.
+
+        The returned `a:path` element has an `a:moveTo` element representing the shape starting
+        point as its only child.
+        """
+        path = sp.add_path(w=self._dx, h=self._dy)
+        path.add_moveTo(*self._local_to_shape(self._start_x, self._start_y))
+        return path
+
+    @property
+    def _top(self):
+        """Return topmost extent of this shape's path in slide coordinates.
+
+        Note that this value does not include any positioning offset; it assumes the drawing
+        (local) coordinate origin is located at slide coordinates (0, 0) (top-left corner of
+        slide).
+        """
+        return int(round(self.shape_offset_y * self._y_scale))
+
+    @property
+    def _width(self):
+        """Return width of this shape's path in slide coordinates.
+
+        This value is based on the actual extents of the shape path and does not include any
+        positioning offset.
+        """
+        return int(round(self._dx * self._x_scale))
+
+
+class _BaseDrawingOperation(object):
+    """Base class for freeform drawing operations.
+
+    A drawing operation has at least one location (x, y) in local coordinates.
+    """
+
+    def __init__(self, freeform_builder: FreeformBuilder, x: Length, y: Length):
+        super(_BaseDrawingOperation, self).__init__()
+        self._freeform_builder = freeform_builder
+        self._x = x
+        self._y = y
+
+    def apply_operation_to(self, path: CT_Path2D) -> CT_DrawingOperation:
+        """Add the XML element(s) implementing this operation to `path`.
+
+        Must be implemented by each subclass.
+        """
+        raise NotImplementedError("must be implemented by each subclass")
+
+    @property
+    def x(self) -> Length:
+        """Return the horizontal (x) target location of this operation.
+
+        The returned value is an integer in local coordinates.
+        """
+        return self._x
+
+    @property
+    def y(self) -> Length:
+        """Return the vertical (y) target location of this operation.
+
+        The returned value is an integer in local coordinates.
+        """
+        return self._y
+
+
+class _Close(object):
+    """Specifies adding a `<a:close/>` element to the current contour."""
+
+    @classmethod
+    def new(cls) -> _Close:
+        """Return a new _Close object."""
+        return cls()
+
+    def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DClose:
+        """Add `a:close` element to `path`."""
+        return path.add_close()
+
+
+class _LineSegment(_BaseDrawingOperation):
+    """Specifies a straight line segment ending at the specified point."""
+
+    @classmethod
+    def new(cls, freeform_builder: FreeformBuilder, x: float, y: float) -> _LineSegment:
+        """Return a new _LineSegment object ending at point *(x, y)*.
+
+        Both `x` and `y` are rounded to the nearest integer before use.
+        """
+        return cls(freeform_builder, Emu(int(round(x))), Emu(int(round(y))))
+
+    def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DLineTo:
+        """Add `a:lnTo` element to `path` for this line segment.
+
+        Returns the `a:lnTo` element newly added to the path.
+        """
+        return path.add_lnTo(
+            Emu(self._x - self._freeform_builder.shape_offset_x),
+            Emu(self._y - self._freeform_builder.shape_offset_y),
+        )
+
+
+class _MoveTo(_BaseDrawingOperation):
+    """Specifies a new pen position."""
+
+    @classmethod
+    def new(cls, freeform_builder: FreeformBuilder, x: float, y: float) -> _MoveTo:
+        """Return a new _MoveTo object for move to point `(x, y)`.
+
+        Both `x` and `y` are rounded to the nearest integer before use.
+        """
+        return cls(freeform_builder, Emu(int(round(x))), Emu(int(round(y))))
+
+    def apply_operation_to(self, path: CT_Path2D) -> CT_Path2DMoveTo:
+        """Add `a:moveTo` element to `path` for this line segment."""
+        return path.add_moveTo(
+            Emu(self._x - self._freeform_builder.shape_offset_x),
+            Emu(self._y - self._freeform_builder.shape_offset_y),
+        )