aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/pptx/util.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/util.py')
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/util.py214
1 files changed, 214 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/util.py b/.venv/lib/python3.12/site-packages/pptx/util.py
new file mode 100644
index 00000000..fdec7929
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/util.py
@@ -0,0 +1,214 @@
+"""Utility functions and classes."""
+
+from __future__ import annotations
+
+import functools
+from typing import Any, Callable, Generic, TypeVar, cast
+
+
+class Length(int):
+ """Base class for length classes Inches, Emu, Cm, Mm, and Pt.
+
+ Provides properties for converting length values to convenient units.
+ """
+
+ _EMUS_PER_INCH = 914400
+ _EMUS_PER_CENTIPOINT = 127
+ _EMUS_PER_CM = 360000
+ _EMUS_PER_MM = 36000
+ _EMUS_PER_PT = 12700
+
+ def __new__(cls, emu: int):
+ return int.__new__(cls, emu)
+
+ @property
+ def inches(self) -> float:
+ """Floating point length in inches."""
+ return self / float(self._EMUS_PER_INCH)
+
+ @property
+ def centipoints(self) -> int:
+ """Integer length in hundredths of a point (1/7200 inch).
+
+ Used internally because PowerPoint stores font size in centipoints.
+ """
+ return self // self._EMUS_PER_CENTIPOINT
+
+ @property
+ def cm(self) -> float:
+ """Floating point length in centimeters."""
+ return self / float(self._EMUS_PER_CM)
+
+ @property
+ def emu(self) -> int:
+ """Integer length in English Metric Units."""
+ return self
+
+ @property
+ def mm(self) -> float:
+ """Floating point length in millimeters."""
+ return self / float(self._EMUS_PER_MM)
+
+ @property
+ def pt(self) -> float:
+ """Floating point length in points."""
+ return self / float(self._EMUS_PER_PT)
+
+
+class Inches(Length):
+ """Convenience constructor for length in inches."""
+
+ def __new__(cls, inches: float):
+ emu = int(inches * Length._EMUS_PER_INCH)
+ return Length.__new__(cls, emu)
+
+
+class Centipoints(Length):
+ """Convenience constructor for length in hundredths of a point."""
+
+ def __new__(cls, centipoints: int):
+ emu = int(centipoints * Length._EMUS_PER_CENTIPOINT)
+ return Length.__new__(cls, emu)
+
+
+class Cm(Length):
+ """Convenience constructor for length in centimeters."""
+
+ def __new__(cls, cm: float):
+ emu = int(cm * Length._EMUS_PER_CM)
+ return Length.__new__(cls, emu)
+
+
+class Emu(Length):
+ """Convenience constructor for length in english metric units."""
+
+ def __new__(cls, emu: int):
+ return Length.__new__(cls, int(emu))
+
+
+class Mm(Length):
+ """Convenience constructor for length in millimeters."""
+
+ def __new__(cls, mm: float):
+ emu = int(mm * Length._EMUS_PER_MM)
+ return Length.__new__(cls, emu)
+
+
+class Pt(Length):
+ """Convenience value class for specifying a length in points."""
+
+ def __new__(cls, points: float):
+ emu = int(points * Length._EMUS_PER_PT)
+ return Length.__new__(cls, emu)
+
+
+_T = TypeVar("_T")
+
+
+class lazyproperty(Generic[_T]):
+ """Decorator like @property, but evaluated only on first access.
+
+ Like @property, this can only be used to decorate methods having only a `self` parameter, and
+ is accessed like an attribute on an instance, i.e. trailing parentheses are not used. Unlike
+ @property, the decorated method is only evaluated on first access; the resulting value is
+ cached and that same value returned on second and later access without re-evaluation of the
+ method.
+
+ Like @property, this class produces a *data descriptor* object, which is stored in the __dict__
+ of the *class* under the name of the decorated method ('fget' nominally). The cached value is
+ stored in the __dict__ of the *instance* under that same name.
+
+ Because it is a data descriptor (as opposed to a *non-data descriptor*), its `__get__()` method
+ is executed on each access of the decorated attribute; the __dict__ item of the same name is
+ "shadowed" by the descriptor.
+
+ While this may represent a performance improvement over a property, its greater benefit may be
+ its other characteristics. One common use is to construct collaborator objects, removing that
+ "real work" from the constructor, while still only executing once. It also de-couples client
+ code from any sequencing considerations; if it's accessed from more than one location, it's
+ assured it will be ready whenever needed.
+
+ Loosely based on: https://stackoverflow.com/a/6849299/1902513.
+
+ A lazyproperty is read-only. There is no counterpart to the optional "setter" (or deleter)
+ behavior of an @property. This is critically important to maintaining its immutability and
+ idempotence guarantees. Attempting to assign to a lazyproperty raises AttributeError
+ unconditionally.
+
+ The parameter names in the methods below correspond to this usage example::
+
+ class Obj(object)
+
+ @lazyproperty
+ def fget(self):
+ return 'some result'
+
+ obj = Obj()
+
+ Not suitable for wrapping a function (as opposed to a method) because it is not callable.
+ """
+
+ def __init__(self, fget: Callable[..., _T]) -> None:
+ """*fget* is the decorated method (a "getter" function).
+
+ A lazyproperty is read-only, so there is only an *fget* function (a regular
+ @property can also have an fset and fdel function). This name was chosen for
+ consistency with Python's `property` class which uses this name for the
+ corresponding parameter.
+ """
+ # --- maintain a reference to the wrapped getter method
+ self._fget = fget
+ # --- and store the name of that decorated method
+ self._name = fget.__name__
+ # --- adopt fget's __name__, __doc__, and other attributes
+ functools.update_wrapper(self, fget) # pyright: ignore
+
+ def __get__(self, obj: Any, type: Any = None) -> _T:
+ """Called on each access of 'fget' attribute on class or instance.
+
+ *self* is this instance of a lazyproperty descriptor "wrapping" the property
+ method it decorates (`fget`, nominally).
+
+ *obj* is the "host" object instance when the attribute is accessed from an
+ object instance, e.g. `obj = Obj(); obj.fget`. *obj* is None when accessed on
+ the class, e.g. `Obj.fget`.
+
+ *type* is the class hosting the decorated getter method (`fget`) on both class
+ and instance attribute access.
+ """
+ # --- when accessed on class, e.g. Obj.fget, just return this descriptor
+ # --- instance (patched above to look like fget).
+ if obj is None:
+ return self # type: ignore
+
+ # --- when accessed on instance, start by checking instance __dict__ for
+ # --- item with key matching the wrapped function's name
+ value = obj.__dict__.get(self._name)
+ if value is None:
+ # --- on first access, the __dict__ item will be absent. Evaluate fget()
+ # --- and store that value in the (otherwise unused) host-object
+ # --- __dict__ value of same name ('fget' nominally)
+ value = self._fget(obj)
+ obj.__dict__[self._name] = value
+ return cast(_T, value)
+
+ def __set__(self, obj: Any, value: Any) -> None:
+ """Raises unconditionally, to preserve read-only behavior.
+
+ This decorator is intended to implement immutable (and idempotent) object
+ attributes. For that reason, assignment to this property must be explicitly
+ prevented.
+
+ If this __set__ method was not present, this descriptor would become a
+ *non-data descriptor*. That would be nice because the cached value would be
+ accessed directly once set (__dict__ attrs have precedence over non-data
+ descriptors on instance attribute lookup). The problem is, there would be
+ nothing to stop assignment to the cached value, which would overwrite the result
+ of `fget()` and break both the immutability and idempotence guarantees of this
+ decorator.
+
+ The performance with this __set__() method in place was roughly 0.4 usec per
+ access when measured on a 2.8GHz development machine; so quite snappy and
+ probably not a rich target for optimization efforts.
+ """
+ raise AttributeError("can't set attribute")