init: 폴더구조 설계 및 인프라 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:31:53 +09:00
parent 34ad612281
commit f9c463f87a
1839 changed files with 323096 additions and 1 deletions
@@ -0,0 +1,40 @@
"""|ChartFormat| and related objects.
|ChartFormat| acts as proxy for the `spPr` element, which provides visual shape properties such as
line and fill for chart elements.
"""
from __future__ import annotations
from pptx.dml.fill import FillFormat
from pptx.dml.line import LineFormat
from pptx.shared import ElementProxy
from pptx.util import lazyproperty
class ChartFormat(ElementProxy):
"""
The |ChartFormat| object provides access to visual shape properties for
chart elements like |Axis|, |Series|, and |MajorGridlines|. It has two
properties, :attr:`fill` and :attr:`line`, which return a |FillFormat|
and |LineFormat| object respectively. The |ChartFormat| object is
provided by the :attr:`format` property on the target axis, series, etc.
"""
@lazyproperty
def fill(self):
"""
|FillFormat| instance for this object, providing access to fill
properties such as fill color.
"""
spPr = self._element.get_or_add_spPr()
return FillFormat.from_fill_parent(spPr)
@lazyproperty
def line(self):
"""
The |LineFormat| object providing access to the visual properties of
this object, such as line color and line style.
"""
spPr = self._element.get_or_add_spPr()
return LineFormat(spPr)
@@ -0,0 +1,301 @@
"""DrawingML objects related to color, ColorFormat being the most prominent."""
from __future__ import annotations
from pptx.enum.dml import MSO_COLOR_TYPE, MSO_THEME_COLOR
from pptx.oxml.dml.color import (
CT_HslColor,
CT_PresetColor,
CT_SchemeColor,
CT_ScRgbColor,
CT_SRgbColor,
CT_SystemColor,
)
class ColorFormat(object):
"""
Provides access to color settings such as RGB color, theme color, and
luminance adjustments.
"""
def __init__(self, eg_colorChoice_parent, color):
super(ColorFormat, self).__init__()
self._xFill = eg_colorChoice_parent
self._color = color
@property
def brightness(self):
"""
Read/write float value between -1.0 and 1.0 indicating the brightness
adjustment for this color, e.g. -0.25 is 25% darker and 0.4 is 40%
lighter. 0 means no brightness adjustment.
"""
return self._color.brightness
@brightness.setter
def brightness(self, value):
self._validate_brightness_value(value)
self._color.brightness = value
@classmethod
def from_colorchoice_parent(cls, eg_colorChoice_parent):
xClr = eg_colorChoice_parent.eg_colorChoice
color = _Color(xClr)
color_format = cls(eg_colorChoice_parent, color)
return color_format
@property
def rgb(self):
"""
|RGBColor| value of this color, or None if no RGB color is explicitly
defined for this font. Setting this value to an |RGBColor| instance
causes its type to change to MSO_COLOR_TYPE.RGB. If the color was a
theme color with a brightness adjustment, the brightness adjustment
is removed when changing it to an RGB color.
"""
return self._color.rgb
@rgb.setter
def rgb(self, rgb):
if not isinstance(rgb, RGBColor):
raise ValueError("assigned value must be type RGBColor")
# change to rgb color format if not already
if not isinstance(self._color, _SRgbColor):
srgbClr = self._xFill.get_or_change_to_srgbClr()
self._color = _SRgbColor(srgbClr)
# call _SRgbColor instance to do the setting
self._color.rgb = rgb
@property
def theme_color(self):
"""Theme color value of this color.
Value is a member of :ref:`MsoThemeColorIndex`, e.g.
``MSO_THEME_COLOR.ACCENT_1``. Raises AttributeError on access if the
color is not type ``MSO_COLOR_TYPE.SCHEME``. Assigning a member of
:ref:`MsoThemeColorIndex` causes the color's type to change to
``MSO_COLOR_TYPE.SCHEME``.
"""
return self._color.theme_color
@theme_color.setter
def theme_color(self, mso_theme_color_idx):
# change to theme color format if not already
if not isinstance(self._color, _SchemeColor):
schemeClr = self._xFill.get_or_change_to_schemeClr()
self._color = _SchemeColor(schemeClr)
self._color.theme_color = mso_theme_color_idx
@property
def type(self):
"""
Read-only. A value from :ref:`MsoColorType`, either RGB or SCHEME,
corresponding to the way this color is defined, or None if no color
is defined at the level of this font.
"""
return self._color.color_type
def _validate_brightness_value(self, value):
if value < -1.0 or value > 1.0:
raise ValueError("brightness must be number in range -1.0 to 1.0")
if isinstance(self._color, _NoneColor):
msg = (
"can't set brightness when color.type is None. Set color.rgb"
" or .theme_color first."
)
raise ValueError(msg)
class _Color(object):
"""
Object factory for color object of the appropriate type, also the base
class for all color type classes such as SRgbColor.
"""
def __new__(cls, xClr):
color_cls = {
type(None): _NoneColor,
CT_HslColor: _HslColor,
CT_PresetColor: _PrstColor,
CT_SchemeColor: _SchemeColor,
CT_ScRgbColor: _ScRgbColor,
CT_SRgbColor: _SRgbColor,
CT_SystemColor: _SysColor,
}[type(xClr)]
return super(_Color, cls).__new__(color_cls)
def __init__(self, xClr):
super(_Color, self).__init__()
self._xClr = xClr
@property
def brightness(self):
lumMod, lumOff = self._xClr.lumMod, self._xClr.lumOff
# a tint is lighter, a shade is darker
# only tints have lumOff child
if lumOff is not None:
brightness = lumOff.val
return brightness
# which leaves shades, if lumMod is present
if lumMod is not None:
brightness = lumMod.val - 1.0
return brightness
# there's no brightness adjustment if no lum{Mod|Off} elements
return 0
@brightness.setter
def brightness(self, value):
if value > 0:
self._tint(value)
elif value < 0:
self._shade(value)
else:
self._xClr.clear_lum()
@property
def color_type(self): # pragma: no cover
tmpl = ".color_type property must be implemented on %s"
raise NotImplementedError(tmpl % self.__class__.__name__)
@property
def rgb(self):
"""
Raises TypeError on access unless overridden by subclass.
"""
tmpl = "no .rgb property on color type '%s'"
raise AttributeError(tmpl % self.__class__.__name__)
@property
def theme_color(self):
"""
Raises TypeError on access unless overridden by subclass.
"""
return MSO_THEME_COLOR.NOT_THEME_COLOR
def _shade(self, value):
lumMod_val = 1.0 - abs(value)
color_elm = self._xClr.clear_lum()
color_elm.add_lumMod(lumMod_val)
def _tint(self, value):
lumOff_val = value
lumMod_val = 1.0 - lumOff_val
color_elm = self._xClr.clear_lum()
color_elm.add_lumMod(lumMod_val)
color_elm.add_lumOff(lumOff_val)
class _HslColor(_Color):
@property
def color_type(self):
return MSO_COLOR_TYPE.HSL
class _NoneColor(_Color):
@property
def color_type(self):
return None
@property
def theme_color(self):
"""
Raise TypeError on attempt to access .theme_color when no color
choice is present.
"""
tmpl = "no .theme_color property on color type '%s'"
raise AttributeError(tmpl % self.__class__.__name__)
class _PrstColor(_Color):
@property
def color_type(self):
return MSO_COLOR_TYPE.PRESET
class _SchemeColor(_Color):
def __init__(self, schemeClr):
super(_SchemeColor, self).__init__(schemeClr)
self._schemeClr = schemeClr
@property
def color_type(self):
return MSO_COLOR_TYPE.SCHEME
@property
def theme_color(self):
"""
Theme color value of this color, one of those defined in the
MSO_THEME_COLOR enumeration, e.g. MSO_THEME_COLOR.ACCENT_1. None if
no theme color is explicitly defined for this font. Setting this to a
value in MSO_THEME_COLOR causes the color's type to change to
``MSO_COLOR_TYPE.SCHEME``.
"""
return self._schemeClr.val
@theme_color.setter
def theme_color(self, mso_theme_color_idx):
self._schemeClr.val = mso_theme_color_idx
class _ScRgbColor(_Color):
@property
def color_type(self):
return MSO_COLOR_TYPE.SCRGB
class _SRgbColor(_Color):
def __init__(self, srgbClr):
super(_SRgbColor, self).__init__(srgbClr)
self._srgbClr = srgbClr
@property
def color_type(self):
return MSO_COLOR_TYPE.RGB
@property
def rgb(self):
"""
|RGBColor| value of this color, corresponding to the value in the
required ``val`` attribute of the ``<a:srgbColr>`` element.
"""
return RGBColor.from_string(self._srgbClr.val)
@rgb.setter
def rgb(self, rgb):
self._srgbClr.val = str(rgb)
class _SysColor(_Color):
@property
def color_type(self):
return MSO_COLOR_TYPE.SYSTEM
class RGBColor(tuple):
"""
Immutable value object defining a particular RGB color.
"""
def __new__(cls, r, g, b):
msg = "RGBColor() takes three integer values 0-255"
for val in (r, g, b):
if not isinstance(val, int) or val < 0 or val > 255:
raise ValueError(msg)
return super(RGBColor, cls).__new__(cls, (r, g, b))
def __str__(self):
"""
Return a hex string rgb value, like '3C2F80'
"""
return "%02X%02X%02X" % self
@classmethod
def from_string(cls, rgb_hex_str):
"""
Return a new instance from an RGB color hex string like ``'3C2F80'``.
"""
r = int(rgb_hex_str[:2], 16)
g = int(rgb_hex_str[2:4], 16)
b = int(rgb_hex_str[4:], 16)
return cls(r, g, b)
@@ -0,0 +1,41 @@
"""Visual effects on a shape such as shadow, glow, and reflection."""
from __future__ import annotations
class ShadowFormat(object):
"""Provides access to shadow effect on a shape."""
def __init__(self, spPr):
# ---spPr may also be a grpSpPr; both have a:effectLst child---
self._element = spPr
@property
def inherit(self):
"""True if shape inherits shadow settings.
Read/write. An explicitly-defined shadow setting on a shape causes
this property to return |False|. A shape with no explicitly-defined
shadow setting inherits its shadow settings from the style hierarchy
(and so returns |True|).
Assigning |True| causes any explicitly-defined shadow setting to be
removed and inheritance is restored. Note this has the side-effect of
removing **all** explicitly-defined effects, such as glow and
reflection, and restoring inheritance for all effects on the shape.
Assigning |False| causes the inheritance link to be broken and **no**
effects to appear on the shape.
"""
if self._element.effectLst is None:
return True
return False
@inherit.setter
def inherit(self, value):
inherit = bool(value)
if inherit:
# ---remove any explicitly-defined effects
self._element._remove_effectLst()
else:
# ---ensure at least the effectLst element is present
self._element.get_or_add_effectLst()
@@ -0,0 +1,398 @@
"""DrawingML objects related to fill."""
from __future__ import annotations
from collections.abc import Sequence
from typing import TYPE_CHECKING
from pptx.dml.color import ColorFormat
from pptx.enum.dml import MSO_FILL
from pptx.oxml.dml.fill import (
CT_BlipFillProperties,
CT_GradientFillProperties,
CT_GroupFillProperties,
CT_NoFillProperties,
CT_PatternFillProperties,
CT_SolidColorFillProperties,
)
from pptx.oxml.xmlchemy import BaseOxmlElement
from pptx.shared import ElementProxy
from pptx.util import lazyproperty
if TYPE_CHECKING:
from pptx.enum.dml import MSO_FILL_TYPE
from pptx.oxml.xmlchemy import BaseOxmlElement
class FillFormat(object):
"""Provides access to the current fill properties.
Also provides methods to change the fill type.
"""
def __init__(self, eg_fill_properties_parent: BaseOxmlElement, fill_obj: _Fill):
super(FillFormat, self).__init__()
self._xPr = eg_fill_properties_parent
self._fill = fill_obj
@classmethod
def from_fill_parent(cls, eg_fillProperties_parent: BaseOxmlElement) -> FillFormat:
"""
Return a |FillFormat| instance initialized to the settings contained
in *eg_fillProperties_parent*, which must be an element having
EG_FillProperties in its child element sequence in the XML schema.
"""
fill_elm = eg_fillProperties_parent.eg_fillProperties
fill = _Fill(fill_elm)
fill_format = cls(eg_fillProperties_parent, fill)
return fill_format
@property
def back_color(self):
"""Return a |ColorFormat| object representing background color.
This property is only applicable to pattern fills and lines.
"""
return self._fill.back_color
def background(self):
"""
Sets the fill type to noFill, i.e. transparent.
"""
noFill = self._xPr.get_or_change_to_noFill()
self._fill = _NoFill(noFill)
@property
def fore_color(self):
"""
Return a |ColorFormat| instance representing the foreground color of
this fill.
"""
return self._fill.fore_color
def gradient(self):
"""Sets the fill type to gradient.
If the fill is not already a gradient, a default gradient is added.
The default gradient corresponds to the default in the built-in
PowerPoint "White" template. This gradient is linear at angle
90-degrees (upward), with two stops. The first stop is Accent-1 with
tint 100%, shade 100%, and satMod 130%. The second stop is Accent-1
with tint 50%, shade 100%, and satMod 350%.
"""
gradFill = self._xPr.get_or_change_to_gradFill()
self._fill = _GradFill(gradFill)
@property
def gradient_angle(self):
"""Angle in float degrees of line of a linear gradient.
Read/Write. May be |None|, indicating the angle should be inherited
from the style hierarchy. An angle of 0.0 corresponds to
a left-to-right gradient. Increasing angles represent
counter-clockwise rotation of the line, for example 90.0 represents
a bottom-to-top gradient. Raises |TypeError| when the fill type is
not MSO_FILL_TYPE.GRADIENT. Raises |ValueError| for a non-linear
gradient (e.g. a radial gradient).
"""
if self.type != MSO_FILL.GRADIENT:
raise TypeError("Fill is not of type MSO_FILL_TYPE.GRADIENT")
return self._fill.gradient_angle
@gradient_angle.setter
def gradient_angle(self, value):
if self.type != MSO_FILL.GRADIENT:
raise TypeError("Fill is not of type MSO_FILL_TYPE.GRADIENT")
self._fill.gradient_angle = value
@property
def gradient_stops(self):
"""|GradientStops| object providing access to stops of this gradient.
Raises |TypeError| when fill is not gradient (call `fill.gradient()`
first). Each stop represents a color between which the gradient
smoothly transitions.
"""
if self.type != MSO_FILL.GRADIENT:
raise TypeError("Fill is not of type MSO_FILL_TYPE.GRADIENT")
return self._fill.gradient_stops
@property
def pattern(self):
"""Return member of :ref:`MsoPatternType` indicating fill pattern.
Raises |TypeError| when fill is not patterned (call
`fill.patterned()` first). Returns |None| if no pattern has been set;
PowerPoint may display the default `PERCENT_5` pattern in this case.
Assigning |None| will remove any explicit pattern setting, although
relying on the default behavior is discouraged and may produce
rendering differences across client applications.
"""
return self._fill.pattern
@pattern.setter
def pattern(self, pattern_type):
self._fill.pattern = pattern_type
def patterned(self):
"""Selects the pattern fill type.
Note that calling this method does not by itself set a foreground or
background color of the pattern. Rather it enables subsequent
assignments to properties like fore_color to set the pattern and
colors.
"""
pattFill = self._xPr.get_or_change_to_pattFill()
self._fill = _PattFill(pattFill)
def solid(self):
"""
Sets the fill type to solid, i.e. a solid color. Note that calling
this method does not set a color or by itself cause the shape to
appear with a solid color fill; rather it enables subsequent
assignments to properties like fore_color to set the color.
"""
solidFill = self._xPr.get_or_change_to_solidFill()
self._fill = _SolidFill(solidFill)
@property
def type(self) -> MSO_FILL_TYPE:
"""The type of this fill, e.g. `MSO_FILL_TYPE.SOLID`."""
return self._fill.type
class _Fill(object):
"""
Object factory for fill object of class matching fill element, such as
_SolidFill for ``<a:solidFill>``; also serves as the base class for all
fill classes
"""
def __new__(cls, xFill):
if xFill is None:
fill_cls = _NoneFill
elif isinstance(xFill, CT_BlipFillProperties):
fill_cls = _BlipFill
elif isinstance(xFill, CT_GradientFillProperties):
fill_cls = _GradFill
elif isinstance(xFill, CT_GroupFillProperties):
fill_cls = _GrpFill
elif isinstance(xFill, CT_NoFillProperties):
fill_cls = _NoFill
elif isinstance(xFill, CT_PatternFillProperties):
fill_cls = _PattFill
elif isinstance(xFill, CT_SolidColorFillProperties):
fill_cls = _SolidFill
else:
fill_cls = _Fill
return super(_Fill, cls).__new__(fill_cls)
@property
def back_color(self):
"""Raise TypeError for types that do not override this property."""
tmpl = "fill type %s has no background color, call .patterned() first"
raise TypeError(tmpl % self.__class__.__name__)
@property
def fore_color(self):
"""Raise TypeError for types that do not override this property."""
tmpl = "fill type %s has no foreground color, call .solid() or .pattern" "ed() first"
raise TypeError(tmpl % self.__class__.__name__)
@property
def pattern(self):
"""Raise TypeError for fills that do not override this property."""
tmpl = "fill type %s has no pattern, call .patterned() first"
raise TypeError(tmpl % self.__class__.__name__)
@property
def type(self) -> MSO_FILL_TYPE: # pragma: no cover
raise NotImplementedError(
f".type property must be implemented on {self.__class__.__name__}"
)
class _BlipFill(_Fill):
@property
def type(self):
return MSO_FILL.PICTURE
class _GradFill(_Fill):
"""Proxies an `a:gradFill` element."""
def __init__(self, gradFill):
self._element = self._gradFill = gradFill
@property
def gradient_angle(self):
"""Angle in float degrees of line of a linear gradient.
Read/Write. May be |None|, indicating the angle is inherited from the
style hierarchy. An angle of 0.0 corresponds to a left-to-right
gradient. Increasing angles represent clockwise rotation of the line,
for example 90.0 represents a top-to-bottom gradient. Raises
|TypeError| when the fill type is not MSO_FILL_TYPE.GRADIENT. Raises
|ValueError| for a non-linear gradient (e.g. a radial gradient).
"""
# ---case 1: gradient path is explicit, but not linear---
path = self._gradFill.path
if path is not None:
raise ValueError("not a linear gradient")
# ---case 2: gradient path is inherited (no a:lin OR a:path)---
lin = self._gradFill.lin
if lin is None:
return None
# ---case 3: gradient path is explicitly linear---
# angle is stored in XML as a clockwise angle, whereas the UI
# reports it as counter-clockwise from horizontal-pointing-right.
# Since the UI is consistent with trigonometry conventions, we
# respect that in the API.
clockwise_angle = lin.ang
counter_clockwise_angle = 0.0 if clockwise_angle == 0.0 else (360.0 - clockwise_angle)
return counter_clockwise_angle
@gradient_angle.setter
def gradient_angle(self, value):
lin = self._gradFill.lin
if lin is None:
raise ValueError("not a linear gradient")
lin.ang = 360.0 - value
@lazyproperty
def gradient_stops(self):
"""|_GradientStops| object providing access to gradient colors.
Each stop represents a color between which the gradient smoothly
transitions.
"""
return _GradientStops(self._gradFill.get_or_add_gsLst())
@property
def type(self):
return MSO_FILL.GRADIENT
class _GrpFill(_Fill):
@property
def type(self):
return MSO_FILL.GROUP
class _NoFill(_Fill):
@property
def type(self):
return MSO_FILL.BACKGROUND
class _NoneFill(_Fill):
@property
def type(self):
return None
class _PattFill(_Fill):
"""Provides access to patterned fill properties."""
def __init__(self, pattFill):
super(_PattFill, self).__init__()
self._element = self._pattFill = pattFill
@lazyproperty
def back_color(self):
"""Return |ColorFormat| object that controls background color."""
bgClr = self._pattFill.get_or_add_bgClr()
return ColorFormat.from_colorchoice_parent(bgClr)
@lazyproperty
def fore_color(self):
"""Return |ColorFormat| object that controls foreground color."""
fgClr = self._pattFill.get_or_add_fgClr()
return ColorFormat.from_colorchoice_parent(fgClr)
@property
def pattern(self):
"""Return member of :ref:`MsoPatternType` indicating fill pattern.
Returns |None| if no pattern has been set; PowerPoint may display the
default `PERCENT_5` pattern in this case. Assigning |None| will
remove any explicit pattern setting.
"""
return self._pattFill.prst
@pattern.setter
def pattern(self, pattern_type):
self._pattFill.prst = pattern_type
@property
def type(self):
return MSO_FILL.PATTERNED
class _SolidFill(_Fill):
"""Provides access to fill properties such as color for solid fills."""
def __init__(self, solidFill):
super(_SolidFill, self).__init__()
self._solidFill = solidFill
@lazyproperty
def fore_color(self):
"""Return |ColorFormat| object controlling fill color."""
return ColorFormat.from_colorchoice_parent(self._solidFill)
@property
def type(self):
return MSO_FILL.SOLID
class _GradientStops(Sequence):
"""Collection of |GradientStop| objects defining gradient colors.
A gradient must have a minimum of two stops, but can have as many more
than that as required to achieve the desired effect (three is perhaps
most common). Stops are sequenced in the order they are transitioned
through.
"""
def __init__(self, gsLst):
self._gsLst = gsLst
def __getitem__(self, idx):
return _GradientStop(self._gsLst[idx])
def __len__(self):
return len(self._gsLst)
class _GradientStop(ElementProxy):
"""A single gradient stop.
A gradient stop defines a color and a position.
"""
def __init__(self, gs):
super(_GradientStop, self).__init__(gs)
self._gs = gs
@lazyproperty
def color(self):
"""Return |ColorFormat| object controlling stop color."""
return ColorFormat.from_colorchoice_parent(self._gs)
@property
def position(self):
"""Location of stop in gradient path as float between 0.0 and 1.0.
The value represents a percentage, where 0.0 (0%) represents the
start of the path and 1.0 (100%) represents the end of the path. For
a linear gradient, these would represent opposing extents of the
filled area.
"""
return self._gs.pos
@position.setter
def position(self, value):
self._gs.pos = float(value)
@@ -0,0 +1,100 @@
"""DrawingML objects related to line formatting."""
from __future__ import annotations
from pptx.dml.fill import FillFormat
from pptx.enum.dml import MSO_FILL
from pptx.util import Emu, lazyproperty
class LineFormat(object):
"""Provides access to line properties such as color, style, and width.
A LineFormat object is typically accessed via the ``.line`` property of
a shape such as |Shape| or |Picture|.
"""
def __init__(self, parent):
super(LineFormat, self).__init__()
self._parent = parent
@lazyproperty
def color(self):
"""
The |ColorFormat| instance that provides access to the color settings
for this line. Essentially a shortcut for ``line.fill.fore_color``.
As a side-effect, accessing this property causes the line fill type
to be set to ``MSO_FILL.SOLID``. If this sounds risky for your use
case, use ``line.fill.type`` to non-destructively discover the
existing fill type.
"""
if self.fill.type != MSO_FILL.SOLID:
self.fill.solid()
return self.fill.fore_color
@property
def dash_style(self):
"""Return value indicating line style.
Returns a member of :ref:`MsoLineDashStyle` indicating line style, or
|None| if no explicit value has been set. When no explicit value has
been set, the line dash style is inherited from the style hierarchy.
Assigning |None| removes any existing explicitly-defined dash style.
"""
ln = self._ln
if ln is None:
return None
return ln.prstDash_val
@dash_style.setter
def dash_style(self, dash_style):
if dash_style is None:
ln = self._ln
if ln is None:
return
ln._remove_prstDash()
ln._remove_custDash()
return
ln = self._get_or_add_ln()
ln.prstDash_val = dash_style
@lazyproperty
def fill(self):
"""
|FillFormat| instance for this line, providing access to fill
properties such as foreground color.
"""
ln = self._get_or_add_ln()
return FillFormat.from_fill_parent(ln)
@property
def width(self):
"""
The width of the line expressed as an integer number of :ref:`English
Metric Units <EMU>`. The returned value is an instance of |Length|,
a value class having properties such as `.inches`, `.cm`, and `.pt`
for converting the value into convenient units.
"""
ln = self._ln
if ln is None:
return Emu(0)
return ln.w
@width.setter
def width(self, emu):
if emu is None:
emu = 0
ln = self._get_or_add_ln()
ln.w = emu
def _get_or_add_ln(self):
"""
Return the ``<a:ln>`` element containing the line format properties
in the XML.
"""
return self._parent.get_or_add_ln()
@property
def _ln(self):
return self._parent.ln